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

时间:2018-10-10 22:36:58

标签: recursion f# bind

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)

3 个答案:

答案 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无法统一,因此很清楚发生了什么。 'abind的返回类型(在没有泛型类型名称的情况下,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更好。