`let f = fun a - >之间的区别F#中的a-1`和`let f a = a-1`

时间:2014-07-17 07:05:00

标签: f# semantics

在F#中,let f = fun a -> a-1let f a = a-1之间有什么区别吗?据我所知,后者只是前者的语法糖。这是对的吗?

我在这里专门研究语义差异,而不是特定编译器如何处理这两种情况的差异。

3 个答案:

答案 0 :(得分:7)

(fun a -> ...)只是一个匿名函数。 F#与C#的不同之处在于函数是一等公民,所以当你将匿名函数绑定到f时,它会给f类型签名val f : int -> int(因为a是推断为int32),就像你在第二个例子中绑定了一个普通的命名函数一样。您可以通过在F#interactive中运行示例来证明它们在语义上是相同的。

编辑:匿名函数甚至支持通常推断但可以明确的泛型,如下所示:

let f<'T> = (fun (x: 'T) -> x)

编辑2:来自F#3.1规范草案(found here)

  

如果值定义的直接右侧是匿名函数,则该值定义被视为函数定义,如下例所示:let f = (fun w -> x + w)

除了设置明显的语法错误之外,这就是说将函数值(即匿名函数)绑定到标识符实际上等同于普通函数定义。它继续说这种等价是“函数式编程的主要内容”,所以你可以肯定这将来不会改变。

出于一般目的,F#在设计时将函数定义和函数值(即委托实例,匿名函数)视为相等,但是当需要将函数定义提升为函数时值,它将使用委托类型FSharpFunc作为编译类型。这是所有高阶函数的情况,例如Array,List,Seq模块等中的函数,因为没有像使用委托那样使用CIL方法作为函数值的实际方法。其他所有东西看起来都像你期望编译的C#或VB.NET - 它只是F#使用FSharpFunc的代表。

编辑3:我还无法添加评论,但关于Tomas的回答,F#编译器不知道如何概括表达式let h = (); fun a -> a但是如果你添加了它,它会接受它自己输入注释,就像尼康的id示例一样。

编辑4:这是一个关于F#如何编译函数的非常粗糙的画面。注意Tomas的例子,他称之为的序列表达式,变成了FSharpFunc,而没有();的等效函数变成了一个真正的CIL方法。这就是F#规范在上面讨论的内容。此外,当您使用常规CIL方法作为值,部分应用程序或其他方式时,编译器将使FSharpFunc将其表示为闭包。 enter image description here

答案 1 :(得分:7)

正如其他人已经提到的,简单的答案是使用let foo x = ...定义一个函数并使用let foo = ...会给你不同的结果(因为价值限制),但在这种情况下不会发生,因为编译器非常聪明,知道它可以安全地将let foo = fun x -> ..视为let foo x = ...

现在,如果您想查看更多详细信息,请尝试以下定义:

let f a = a              // Function 'a -> 'a
let g = fun a -> a       // Function 'a -> 'a 
let h = (); fun a -> a   // error FS0030: Value restriction

因此,编译器将使用let foo = fun x -> ...定义的函数视为普通函数,只要在创建&amp;之前的代码之前没有其他内容。返回函数。如果你做了任何事情(即使它只是忽略了单位价值),那么就会有所不同。

如果您想了解更多信息,那么F#3.0规范中的 14.6.7 Generalization 部分列出了所有可推广的值(即上述表达式)作品):

  

以下表达式是可推广的:

     
      
  • 函数表达式
  •   
  • 实现接口的对象表达式
  •   
  • 委托表达
  •   
  • 一个“let”定义表达式,其中定义的右侧和表达式的主体都是可推广的
  •   
  • 一个“let rec”定义表达式,其中所有定义的右侧和表达式的主体都是可推广的
  •   
  • 元组表达式,其所有元素都可以推广
  •   
  • 记录表达式,其所有元素都是可推广的,其中记录不包含可变字段
  •   
  • 一个union case表达式,其所有参数都是可推广的
  •   
  • 一个异常表达式,其所有参数都是可推广的
  •   
  • 空数组表达式
  •   
  • 常量表达
  •   
  • 具有GeneralizableValue属性的类型函数的应用程序。
  •   

重要的是第一点 - 函数表达式(例如fun x -> x)是可推广的;排序表达式(例如(); fun x -> x)不可推广,因此它的行为与普通函数不同。

答案 2 :(得分:5)

您提供的示例在语义上是相同的,但F#编译器与它有关。

让我们看一下不同的(通用)函数:

// val singleton1 : x:'a -> List<'a>
let singleton1 x = [x]

// val singleton2 : x:'a -> List<'a>
let singleton2 = fun x -> [x]

如您所见,签名是相同的。但是如果你考虑一下,这两个不应该是一样的:第一个是真正的函数(编译成.NET方法),但第二个只是一个持有函数的值(委托或Func)在C#)。但是.NET运行时中没有通用值。这只能起作用,因为F#编译器足够智能,可以使singleton2成为一个函数。

你可以在这里看到我的意思:

let singleton3 = id >> fun x -> [x]

现在我们已经超越了F#编译器,由于Value Restriction(向下滚动到主题),它不会编译,即使它在语义上应该与singleton2相同。

所以,总结一下:从语义的角度来看,你的定义是相同的,但由于.NET运行时的限制,F#编译器必须做一些额外的工作才能启用它。


我记得的另一个不同之处是只有功能可以标记为inline

let inline f a = a-1 // OK
let inline f = fun a -> a-1 // Error