Recursion “处理”;当统一“时,结果类型将是无限的”;

Recursion “处理”;当统一“时,结果类型将是无限的”;,recursion,f#,bind,Recursion,F#,Bind,让rec绑定xf=fx |>bind 绑定“Hello World”(fun x->x.toUpper())printf“%s” 上面的代码段导致此错误“统一时,结果类型将是无限的” 编译器建议:“基于此程序点之前的信息查找不确定类型的对象。在此程序点之前可能需要类型批注来约束对象的类型。这可能允许解析查找。” 正如编译器所说,添加类型注释可以解决这个问题。如何编写满足编译器要求的类型注释 这可能会有帮助。因为Javascript更宽容,而且不会抱怨,所以您可以看一个将同一段代码翻译成JS的工作

让rec绑定xf=fx |>bind
绑定“Hello World”(fun x->x.toUpper())printf“%s”

上面的代码段导致此错误“统一时,结果类型将是无限的”

编译器建议:“基于此程序点之前的信息查找不确定类型的对象。在此程序点之前可能需要类型批注来约束对象的类型。这可能允许解析查找。”

正如编译器所说,添加类型注释可以解决这个问题。如何编写满足编译器要求的类型注释

这可能会有帮助。因为Javascript更宽容,而且不会抱怨,所以您可以看一个将同一段代码翻译成JS的工作示例

constbind=x=>f=>bind(f(x))
绑定(“你好世界”)
(x=>x.toUpperCase())

(console.log)
问题在于,您试图定义的
bind
函数没有可以用标准F#表示的类型。您需要一个类型如下的函数:

'A -> ('A -> 'B) -> ('B -> 'C) -> ('C -> 'D) -> ('D -> 'E) -> ....
换句话说,
bind
函数应该接受输入和应用于它们的一系列函数,但不限制序列的长度。标准F#type系统没有表达这一点的方式,因此人们通常使用其他模式。在您的情况下,它将是
|>
操作符。与您的
绑定
不同,您必须反复编写
|>
,但这通常很好:

"Hello World" 
|> (fun x -> x.ToUpper()) 
|> printf "%s"
也就是说,我认为你的例子选得不好,我只想写:

printf "%s" ("Hello world".ToUpper())

最后,实际上可以使用重载定义类似于
bind
函数的内容,这是由于F#静态成员约束而实现的。这是一个很好的解决方案,但是对于你的例子来说,这是一个惯用的、干净的解决方案。

总结:你不能这样做,但是这个函数在F#中没有用处;只需使用
|>
操作符即可

更长版本:无法以满足F#编译器要求的方式对所描述的
bind
函数进行注释。当我将
let rec bind x f=f x |>bind
粘贴到f#Interactive时,会出现以下错误:

如果我们将定义重新排列一点,使其看起来像
let rec bind x f=bind(f x)
,我们会得到一个稍微简化的类型错误:

通过一点类型暗示(
let bind(x:'x)(f:'f)=……
),我们得到了一个错误,即类型
'a
'f->'a
无法统一,因此发生了什么事情变得很清楚
'a
bind
的返回类型(如果泛型类型没有任何名称,则F#分配以
'a
开头的名称)。现在让我们看看为什么会发生这种类型的错误

看起来您已经了解了部分应用程序:任何两个参数的函数,当给定一个参数时,都会返回一个函数,在计算函数体之前等待其第二个参数。换句话说,f#中的让f a b=…等价于Javascript
const f=a=>b=>…
。这里,
bind
函数在给定单个参数
x
时,返回一个函数,该函数在计算
bind
主体之前等待
f
。这意味着当
bind
被传递一个参数时,它的返回类型是
'f->'a
(其中
'a
,正如我们所说,是f#编译器任意分配给
bind
结果的名称)

然而,这里是类型冲突产生的地方:值
bind(fx)
,它的值为
'f->'a
,正如我们已经说过的,它也是
bind
函数的结果。这意味着它应该具有类型
'a
。因此F#编译器需要编译该函数,使类型
'a
'F->'a
的类型相同。如果这是可能的,那么在类型代数中,
'a='f->'a
,然后你可以将方程右侧的
'a
展开为
'f->'a
,这样方程就变成了
'a='f->('f->'a)
。现在可以再次展开
'a
,得到
'a='f->('f->('f->'a))
。以此类推。F#编译器不允许无限扩展类型,因此这是不允许的

但是正如我已经指出的(Tomas Petricek解释过的),在F#中实际上不需要这个
bind
函数。它只是一种将函数挂接到管道中的方法,其中一个函数的输出将传递到下一个函数的输入中(正如您的Javascript示例所示)。在F#中,实现这一点的惯用方法是使用“pipeline”操作符。与绑定“输入值”f1 f2 f3(其中f1、f2和f3是三个适当类型的函数)不同,您只需在F#中写入:

"input value"
|> f1
|> f2
|> f3

这是正常的、惯用的F#,几乎所有人都能理解,即使是那些不太熟悉函数式编程的人。因此,在F#中没有必要使用这个
bind
函数。

bind
的定义是一个函数悖论。这是自相矛盾的

let rec bind a f = f a |> bind
//  val bind: (a:'a) -> (f:'a->'a) -> 'b
查看它的最佳方法是添加一些类型注释,让我们用
int
替换
'a
,使其更具体:

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
事实上,您可以注释
循环let rec bind (a:int) (f:int->int) = f a |> bind
//  val bind:(a:int) -> (f:int->int)-> 'a
let rec loop() = printfn "Hello" ; loop()
//  val loop : unit -> 'a
let rec loop() : float = printfn "Hello" ; loop()
//  val loop : unit -> float
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'