在F#中,let f = fun a -> a-1
和let f a = a-1
之间有什么区别吗?据我所知,后者只是前者的语法糖。这是对的吗?
我在这里专门研究语义差异,而不是特定编译器如何处理这两种情况的差异。
答案 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将其表示为闭包。
答案 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