考虑以下F#: -
type TestClass() =
let getValFromMap m k = Map.find k m
let addToMap map k i = map |> Map.add k i
let mutable someMap : Map<string,int> = Map.empty
let getValFromMapPartial key = getValFromMap someMap key
let getValFromMapPartialAndTacit = getValFromMap someMap
member this.AddThenGet() =
someMap <- addToMap someMap "A" 10
let value = getValFromMapPartial "A"
printfn "Value from partial = %i" value // prints out
let value = getValFromMapPartialAndTacit "A" // throws
printfn "Value from partial and tacit = %i" value
[<EntryPoint>]
let main argv =
let test = TestClass()
test.AddThenGet()
0
在我看来,函数getValFromMapPartial
和getValFromMapPartialAndTacit
是完全相同的。 F#表示它们具有完全相同的类型:(string -> int)
。然而它们的表现却截然不同,而且它们的编译方式也大相径庭。使用dotPeek进行反编译,我发现getValFromMapPartial
是一种方法,而getValFromMapPartialAndTacit
是在ctor中初始化的字段。
即使在最高警告级别(VS 2012和2013),F#也不会抱怨getValFromMapPartialAndTacit
。然而,在上面的示例中调用此函数失败了,大概是因为它已经包装了someMap
的初始空版本,尽管它是可变的。
为什么这两个功能有区别?如果F#发出警告,默认/无点版本可能会失败吗?
答案 0 :(得分:3)
F#编译器区分具有参数的函数的let-bindings和没有参数的 values 。
值定义:像let a = ...
这样的绑定是值定义。在对代码中的任何进一步评估之前,对其主体进行了热切评估,并且#34;在此之前。
函数定义:像let f x = ...
这样的绑定是一个语法函数定义,在调用函数时的内容被评估。< / p>
由于someMap
引用了一个可变变量,因此在函数定义中使用此变量意味着在调用函数时从变量中读取。但是,getValFromMapPartialAndTacit
中的用法会在声明时读取值。
此行为不会阻止值成为函数。您也可以编写let f = fun x -> ...
来声明一个函数,...
将再次成为函数定义的一部分。但是,如果要在=
和fun
之间添加定义,则会在f
的定义时对其进行评估,而不是在调用它时。
在问题的评论中,someMap
是一个可变参考单元时会出现同样的问题。这是同样的问题。该函数由Andrew重写为可变参考单元格:
let getValFromMapPartialAndTacit = getValFromMap !someMap
这里,取值引用运算符(!)
在绑定值时应用,而不是在调用函数时应用。它相当于:
let mapRightNow = !someMap
let getValFromMapPartialAndTacit = getValFromMap mapRightNow
答案 1 :(得分:2)
getValFromMapPartial
是一个真正的句法函数。它的签名是val getValFromMapPartial : key:string -> int
。无论何时调用它,它都使用当前值someMap
。这就是为什么它适用于你的例子;它访问具有条目的someMap
版本。
另一方面,getValFromMapPartialAndTacit
是 lambda计算函数。它的签名是val getValFromMapPartialAndTacit : (string -> int)
(注意括号)。 lambda有一个编译器生成的闭包,它在计算lambda时包含someMap
的版本。这就是为什么它在你的例子中不起作用;它总是能够访问没有条目的someMap
原始版本。