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=…等价于Javascriptconst 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'