let rec bind x f = f x |> bind
bind "Hello World" (fun x -> x.toUpper()) printf "%s"
上面的代码段导致此错误“统一时结果类型将是无限的”。
编译器的建议:“基于此程序点之前的信息对不确定类型的对象进行查找。在此程序点之前可能需要类型注释,以约束对象的类型。这可以使查找得以解决。 。”
如编译器所说,添加类型注释将解决此问题。您将如何编写满足编译器要求的类型注释?
这可能会有所帮助。由于Javascript更具宽容性,而且不会抱怨,因此您可以查看翻译成JS的同一段代码的有效示例。
const bind = x => f => bind(f(x))
bind("Hello World")
(x => x.toUpperCase())
(console.log)
答案 0 :(得分:2)
问题是您尝试定义的bind
函数没有可在标准F#中表达的类型。您需要一个类型如下的函数:
'A -> ('A -> 'B) -> ('B -> 'C) -> ('C -> 'D) -> ('D -> 'E) -> ....
换句话说,bind
函数应该接受输入和适用于它们的一系列函数,但是您并没有限制序列的长度。标准的F#类型系统没有这种表达方式,因此人们通常使用其他模式。在您的情况下,它将是|>
运算符。与您的bind
相比,您必须重复编写|>
,但这通常可以:
"Hello World"
|> (fun x -> x.ToUpper())
|> printf "%s"
也就是说,我认为您的示例选择不当,我只写:
printf "%s" ("Hello world".ToUpper())
最后,实际上可以使用重载来定义类似bind
的函数,这要归功于F#静态成员约束。 those lines的某个方面,但是|>
是一种惯用的,干净的解决方案,用于执行您的示例所说明的事情。
答案 1 :(得分:1)
摘要:您无法执行此操作,但是该功能在F#中没有用;只需使用|>
运算符即可。
更长的版本:无法用满足F#编译器的方式注释您所描述的bind
函数。当我将let rec bind x f = f x |> bind
粘贴到F#Interactive中时,出现以下错误:
error FS0001: Type mismatch. Expecting a ''a -> 'b' but given a ''a -> ('a -> 'a) -> 'b' The types ''b' and '('a -> 'a) -> 'b' cannot be unified.
如果我们稍微重新定义一下,使其看起来像let rec bind x f = bind (f x)
,则会得到稍微简化的类型错误:
error FS0001: Type mismatch. Expecting a ''a' but given a ''b -> 'a' The types ''a' and ''b -> 'a' cannot be unified.
通过一些类型提示(let bind (x : 'x) (f : 'f) = ...
),我们得到以下错误:类型'a
和'f -> 'a
无法统一,因此很清楚发生了什么。 'a
是bind
的返回类型(在没有泛型类型名称的情况下,F#会以'a
开头分配它们)。现在让我们看看为什么会发生这种类型的错误。
您似乎已经对部分应用程序有所了解:任何两个参数的函数在给定单个参数时,都会返回一个函数,该函数在评估函数主体之前会等待其第二个参数。换句话说,F#中的let f a b = ...
等同于Javascript const f = a => b => ...
。在此,bind
函数在被赋予单个参数x
时,返回一个在评估f
的主体之前等待bind
的函数。这意味着当传递bind
的单个参数时,其返回类型为'f -> 'a
(其中'a
是F#编译器已任意分配给的结果的名称。 bind
。
但是,这里发生类型冲突的地方是:值bind (f x)
,正如我们已经说过的,其值是'f -> 'a
,也是您的{{1 }}函数。这意味着它应该具有类型bind
。因此,F#编译器将需要以某种方式编译该函数,以使类型'a
与'a
相同。如果可能的话,请使用代数'f -> 'a
,然后将等式右侧的'a = 'f -> 'a
展开为'a
,这样等式变为{{ 1}}。您现在可以再次展开'f -> 'a
,得到'a = 'f -> ('f -> 'a)
。等等无限。 F#编译器不允许无限扩展类型,因此这是不允许的。
但是正如我已经指出的(和Tomas Petricek解释的那样),您实际上在F#中不需要此'a
函数。所有这些都是将函数挂接到管道中的一种方法,其中一个函数的输出将传递到下一个函数的输入中(如您的Javascript示例所示)。在F#中,惯用的方法是使用“管道”运算符。代替'a = 'f -> ('f -> ('f -> 'a))
(其中f1,f2和f3是适当类型的三个函数),在F#中,您只需编写:
bind
这是正常的,惯用的F#,几乎所有的人都可以理解,甚至那些对函数式编程不太熟悉的人也可以理解。因此,在F#中无需使用此bind "input value" f1 f2 f3
函数。
答案 2 :(得分:0)
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}在接收1个参数时返回“字符串”。这是一个悖论,就像“我总是撒谎”这样的说法。
如果我们返回不带类型注释的初始定义,我们可以看到,绑定的推断结果类型为'b
,表示任何类型,但它必须是一个特定类型,而不是很多类型或每次通话都会发生变化。特别是'b
不能等于('a->'a) -> 'b
,因为它涉及'b
,因此是一个循环定义(或椭圆形),因此是无限循环。
对于Javascript来说,这不是问题,因为它并不关心将什么类型传递给函数或从函数返回什么类型。那就是它们的关键功能,使得F#的使用比Javascript更好。