F# 错误:“要么使‘it’的参数显式,要么如果您不希望它是通用的,请添加类型注释。”

Smo*_*Ken 2 f# haskell type-systems functional-programming

在 F# 交互式 shell 中dotnet fsi,我尝试flip像 Haskell 中那样测试函数,

flip :: (a -> b -> c) -> b -> a -> c

> let flip = fun f -> fun a -> fun b -> f(b)(a);;
val flip: f: ('a -> 'b -> 'c) -> a: 'b -> b: 'a -> 'c
Run Code Online (Sandbox Code Playgroud)

然后,研究内置管道运算符,

> (|>);;
val it: ('a -> ('a -> 'b) -> 'b)
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好。

现在,

> flip (|>);;

  flip (|>);;
  ^^^^^^^^^

/..... : error FS0030: Value restriction. The value 'it' has been inferred to have generic type
    val it: (('_a -> '_b) -> '_a -> '_b)    
Either make the arguments to 'it' explicit or, if you do not intend for it to be generic, add a type annotation.
Run Code Online (Sandbox Code Playgroud)

有人可以解释 F# 类型系统中的此错误是怎么回事吗?

大部头书,

val it: (('_a -> '_b) -> '_a -> '_b)实际上应该是预期的结果,我该如何解决这个问题?谢谢。

Fyo*_*kin 6

之前的回答解释了价值限制是什么以及它发生的原因。但现在你的下一个问题是:好吧,那我该怎么办呢?

正如错误消息本身所暗示的那样,有两种可能的方法:(1) 为其提供显式参数,或 (2) 如果您不希望它是通用的,请添加类型注释。

1. 明确的论据

let flippedPipe x = flip (|>) x
Run Code Online (Sandbox Code Playgroud)

尽管逻辑上这与 相同let flippedPipe = flip (|>),但从语法上讲,这现在是一个函数,而不是一个值。对于值限制来说,语法才是最重要的。

2. 类型注解

let flippedPipe : (int -> int) -> int -> int = flip (|>)
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为该函数不再是通用的,因此值限制不适用。在许多情况下,这是一个理想的选择,但根据您在这里使用的功能类型来判断,我认为这不是您在这种情况下想要的。

3. 显式类型参数

错误消息没有提到这一点,公平地说,它可以被认为是选项(2)的变体。这个想法是你可以给你的函数显式类型参数,如下所示:

let flippedPipe<'a, 'b> = flip (|>)
Run Code Online (Sandbox Code Playgroud)

这使得值限制消失,因为即使它在技术上仍然适用,但您添加类型参数的事实可能表明您知道自己在做什么,因此编译器会关闭。

然而,虽然乍一看似乎可行,但实际上却是错误的。如果您查看此函数的推断类型,您将看到:

val flippedPipe : (obj -> obj) -> obj  -> obj
Run Code Online (Sandbox Code Playgroud)

发生这种情况是因为,即使您添加了类型参数,您也没有准确指定它们的去向。对于编译器所知,它们可能根本不会被使用(又名“幻像类型”)。

因此,为了使其正常工作,应该这样定义:

let flippedPipe<'a, 'b> : ('a -> 'b) -> 'a -> 'b = flip (|>)
Run Code Online (Sandbox Code Playgroud)