F#的隐藏特性

23

这是类似 C# 问题的坦诚尝试。

那么,你最喜欢的 F# 隐藏(或非隐藏)功能是什么?

到目前为止,我使用的大多数功能并不完全是隐藏的,但确实很令人耳目一新。比如说,与 C# 或 VB.NET 相比,重载运算符是多么简单。

Async<T> 帮助我摆脱了一些真正丑陋的代码。

由于我对这种语言还很新,因此了解在实际应用中使用的其他功能将非常有帮助。

11个回答

29

用户可以通过提供一个模块来定义自己的数字字面量,该模块的名称以 NumericLiteral 开头并定义某些方法(FromZeroFromOne 等)。

特别地,可以使用此功���为调用 LanguagePrimitives.GenericZeroLanguagePrimitives.GenericOne 提供更易读的语法:

module NumericLiteralG = begin
  let inline FromZero() = LanguagePrimitives.GenericZero
  let inline FromOne() = LanguagePrimitives.GenericOne
end

let inline genericFactorial n =
  let rec fact n = if (n = 0G) then 1G else n * (fact (n - 1G))
  fact n

let flt = genericFactorial 30.
let bigI = genericFactorial 30I

2
很棒的想法 - 应该默认启用。 - forki23
这是一个非常好的技巧,但似乎只适用于G、N、Q、R和Z。 - Gus

10

F#有一个鲜为人知的功能叫做“签名文件”。您可以拥有一个大实现文件,其中包含许多公共类型/方法/模块/函数,但是您可以通过签名文件 隐藏和选择性地暴露该功能给程序中后面的内容。也就是说,签名文件充当了一种屏幕/过滤器的作用,使您能够将实体“公开给此文件”但“对程序的其余部分保密”。

我认为这是.Net平台上的一个非常重要的特性,因为您可以用它来进行封装,而使用的唯一其他/之前的工具是程序集。如果您有一个小组件,其中包含一些相关类型,这些类型想要能够看到彼此的内部详细信息,但不希望这些类型都对所有人公开,则可以怎么做?有两个方法:

  1. 您可以将该组件放入单独的程序集中,并使那些类型共享的成员为“internal”,将要公开给其他人的部分设置为“public”,或者
  2. 您只需将内部内容标记为“internal”,但将这些类型留在巨大的程序集中,并希望程序集中的所有其他代码都选择不调用那些仅标记为“internal”的成员,因为只有一个其他类型需要看到它。

在我参与的大型软件项目中,我的经验是每个人都会选择第二种方法,因为第一种方法由于各种原因(人们不想要50个小程序集,他们想要1个或2个或3个大型程序集,与我提出的封装点无关的其他好原因(另外:每个人都提到了ILMerge,但没有人使用它))而不可行。

那么您就选择了第二种方法。然后一年后,您终于决定重构该组件,发现在过去的一年中,还有17个其他位置调用了那个“internal”方法,而这个方法只是为了让另一个类型调用,使得现在非常难以重构该组件,因为现在所有人都依赖于那些实现细节。很遗憾。

问题在于,在.NET中没有好的方法来创建适度大小的程序集级封装范围/边界。往往“internal”太大,“private”太小。

... 直到有了F#。通过使用F#签名文件,您可以通过在实现文件中将一堆东西标记为public来创建“此源代码文件”的封装范围,因此文件中的所有其他代码都可以看到它并对其进行操作,但然后使用签名文件隐藏除组件向世界其他部分公开的狭窄公共接口之外的所有细节。这是愉快的。在一个文件中定义三个高度相关的类型,让它们看到彼此的实现细节,但仅向其他人公开真正的公共内容。胜利!

签名文件也许不是程序集内封装边界的理想功能,但它们是我所知道的唯一这样的功能,因此我像漂浮在海洋中的救生筏一样依靠它们。

简而言之:

复杂性是敌人。封装边界是对抗这个敌人的武器。 “private”是一个很好的武器,但有时太小而无法应用,“internal”通常太弱,因为整个程序集和所有InternalsVisibleTo都可以看到内部内容。 F#提供了比“对于类型为private”更大但比“整个程序集”小的作用域,这非常有用。


9

我想知道如果添加了什么会发生

<appSettings>
  <add key="fsharp-navigationbar-enabled" value="true" />
</appSettings>

你想将此代码添加到devenv.exe.config文件中吗?(使用需谨慎)


5
你能否告诉我们,而不是让我们亲自尝试呢? - Dmitri Nesteruk
有没有任何理由我不应该保留它?它很好用。你知道它是否会在下一个版本中出现吗? - Daniel
它没有经过充分测试,可能会使 VS 更容易崩溃。目前还不知道它是否会在 vNext 中出现...还有可能是“解决方案导航器”,我们必须看看能否安排进日程表... - Brian

8
--warnon:1182传递给编译器可以打开有关未使用变量的警告;以下划线开头的变量名不受影响。

谢谢!这不在编译器选项页面上!真的很隐蔽。 - Nels Beckman
1
有趣的是,在 F# 2.0 中,编译器会发出以下警告: FSC:警告FS0075:命令行选项“--warnon”仅供内部使用 - Nels Beckman

7

自动生成代数数据类型比较函数(基于词典排序)是一个相对较为未知的好功能;请参见这个例子


6

是的,F# 没有任何“隐藏”的特性,但它确实将许多强大的功能打包到简单的语言中。这门语言很少被人知道的一个特性是,即使 F# 是静态类型的,你仍然可以启用鸭子类型


6

请查看此问题:

F#运算符“?”

了解问号运算符及其如何提供基本语言机制来构建类似于C#中的“dynamic”功能的信息。


5

虽然不是真的“隐藏”,但作为一个非机器学习专业人员,我很长一段时间没有意识到这一点:

模式匹配可以将数据结构“任意深度”地分解。

这里有一个[非常随意]的嵌套元组示例;它适用于列表、联合或任何组合的嵌套值:

let listEven =
  "Manipulating strings can be intriguing using F#".Split ' '
  |> List.ofArray
  |> List.map (fun x -> (x.Length % 2 = 0, x.Contains "i"), x)
  |> List.choose 
     ( function (true, true), s -> Some s 
              | _, "F#"         -> Some "language" 
              | _               -> None ) 

3
作为实用脚本语言,F#的使用可能被低估了。F#爱好者往往是量化分析师。有时候,你需要比批处理更强大的东西来备份你的MP3(或数十个数据库服务器)。我一直在寻找现代替代jscript / vbscript的工具。最近,我使用了IronPython,但F#可能更完整,而且.NET交互不那么麻烦。
我喜欢柯里化函数的娱乐价值。向一个纯过程式/OOP程序展示一个柯里化函数至少会让人惊叹三次。不过,以此为起点来转换成F#信徒是不好的 :)

我喜欢使用 FSI 编写小脚本,实际上我已经用过一些 .fsx 文件。然而,至少在我的经验中,.fsx 文件所需的启动时间对于通用脚本编写来说太长了。我仍更喜欢使用批处理文件和 Perl 脚本进行脚本编写。 - Juliet
尝试使用PowerShell,你不会后悔的 :) - stej

2

泛型类型的内联运算符可以有不同的泛型约束:

type 'a Wrapper = Wrapper of 'a with
  static member inline (+)(Wrapper(a),Wrapper(b)) = Wrapper(a + b)
  static member inline Exp(Wrapper(a)) = Wrapper(exp a)

let objWrapper = Wrapper(obj())
let intWrapper = (Wrapper 1) + (Wrapper 2)
let fltWrapper = exp (Wrapper 1.0)

(* won''t compile *)
let _ = exp (Wrapper 1)

只是为了澄清一下,如果没有将(+)和Exp声明为内联函数会发生什么? - Joh
@Joh: 运算符将不再具有多态性,因此例如您只能在程序中对单个类型的Wrapper使用(+)运算符。我上面编写的所有代码仍将起作用,但是使用内联运算符,我们也可以添加两个float Wrappers - kvb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接