为什么这个无点F#函数的行为与非点免费版本不同?

时间:2014-11-20 14:54:08

标签: f# pointfree partial-application tacit-programming

考虑以下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

在我看来,函数getValFromMapPartialgetValFromMapPartialAndTacit是完全相同的。 F#表示它们具有完全相同的类型:(string -> int)。然而它们的表现却截然不同,而且它们的编译方式也大相径庭。使用dotPeek进行反编译,我发现getValFromMapPartial是一种方法,而getValFromMapPartialAndTacit是在ctor中初始化的字段。

即使在最高警告级别(VS 2012和2013),F#也不会抱怨getValFromMapPartialAndTacit。然而,在上面的示例中调用此函数失败了,大概是因为它已经包装了someMap的初始空版本,尽管它是可变的。

为什么这两个功能有区别?如果F#发出警告,默认/无点版本可能会失败吗?

2 个答案:

答案 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原始版本。