bind 的定义是一个功能悖论。这是自相矛盾的。
let rec bind a f = f a |> bind
// val bind: (a:'a) -> (f:'a->'a) -> 'b
查看它的最佳方法是添加一些类型注释,让我们将'a 替换为int 以使其更具体:
let rec bind (a:int) (f:int->int) = f a |> bind
// val bind:(a:int) -> (f:int->int)-> 'a
bind 接收一个数字和一个接受该数字并返回另一个数字的函数,但 bind 返回什么?系统不知道,因为它从来没有真正返回任何东西,它只会越来越深入地进入另一个级别的柯里化。这本身不是问题,F# 可以处理永不退出的例程,例如:
let rec loop() = printfn "Hello" ; loop()
// val loop : unit -> 'a
事实上,您可以使用任何类型注释 loop,F# 也可以这样做:
let rec loop() : float = printfn "Hello" ; loop()
// val loop : unit -> float
但如果我们对 bind 做同样的事情,矛盾就会变得明显:
let rec bind (a:int) (f:int->int) : string = f a |> bind
// val bind:(a:int) -> (f:int->int)-> string
错误信息说:
Expecting a 'int -> string' but given a 'int -> (int -> int) -> string'
为什么期待int -> string?因为这是类型所说的。我们知道f a 返回一个int,我们将它传递给bind,我们应该得到一个string,因为这是函数的最终结果。但是bind在只传一个参数的时候并没有返回string,而是返回了(f:int->int)-> string类型的函数,矛盾就在这里。左侧(=)表示bind 在接收 2 个参数时返回 string,但右侧表示它在接收 1 个参数时返回“字符串”。这是一个悖论,就像“我总是撒谎”这样的陈述。
如果我们回到没有类型注释的初始定义,我们可以看到 bind 的推断结果类型是'b,这意味着任何类型,但它必须是一种特定类型,而不是很多类型,或者是一种会发生变化的类型每次通话。特别是'b不能等于('a->'a) -> 'b,因为它涉及'b,因此是一个圆形定义(或者可能是椭圆形),因此是无限循环。
对于 Javascript,这不是问题,因为它不关心传递给函数或从函数返回什么类型。这就是使 F# 比 Javascript 更易于使用的关键特性。