了解 F# 值限制

MyB*_*g18 3 f#

我正在学习 F#。我来这里是因为我对价值限制有一些难以理解的事情。

\n\n

以下是我正在学习的书中的示例。

\n\n
let mapFirst = List.map fst\n
Run Code Online (Sandbox Code Playgroud)\n\n

由于我用 haskell 学习了 FP,所以我非常确定这段代码可以很好地编译,但事实并非如此。结果出现错误FS0030(抱歉,我无法复制粘贴 fsi 错误消息,因为它是用韩语编写的)。相反,我必须提供一个明确的论点,例如:

\n\n
let mapFirst inp = List.map fst inp   // or inp |> List.map fst\n
Run Code Online (Sandbox Code Playgroud)\n\n

但为什么?我认为通过上面的例子,编译器肯定可以推断出给定值的类型:

\n\n
let mapFirst inp = List.map fst inp   // or inp |> List.map fst\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果我没记错的话,我在 haskell 中调用了这个东西eta-conversion,上面两个例子是完全相同的。(但也许不完全是)。为什么我应该显式地向可以柯里化的函数提供参数而不丢失任何信息?

\n\n

我明白了类似的事情

\n\n
val mapFirst : (\'a * \'b) list -> \'a list\n
Run Code Online (Sandbox Code Playgroud)\n\n

不会编译,为什么,但我不认为它与我的问题有关。

\n\n

\xe2\x80\xbb 我看了一下这个问题,但没有帮助。

\n

Fyo*_*kin 5

这与可变性有关。

考虑这个片段:

type T<'a> = { mutable x : 'a option }

let t = { x = None }
Run Code Online (Sandbox Code Playgroud)

tis的类型T<'a>- 也就是说,t是通用的,它有一个通用参数'a,意味着t.x可以是任何类型 - 无论消费者选择什么。

然后,假设您在程序的一部分中执行以下操作:

t.x <- Some 42
Run Code Online (Sandbox Code Playgroud)

完全合法:访问时t您选择'a = int然后t.x : int option,这样您就可以推Some 42入它。

然后,假设您在程序的另一部分执行以下操作:

t.x <- Some "foo"
Run Code Online (Sandbox Code Playgroud)

哦,不,现在会发生什么?是t.x : int option还是是string option?如果编译器忠实地编译了您的代码,则会导致数据损坏。所以编译器会拒绝,以防万一。

由于通常编译器无法真正检查类型内部是否存在可变的内容,因此它采用安全路线并拒绝被推断为通用的值(意味着“不是函数”)。


请注意,这适用于语法值,而不是逻辑值。即使您的值实际上是一个函数,但在语法上没有这样定义(即缺少参数),值限制仍然适用。作为一个例子,请考虑一下:

type T<'a> = { mutable x : 'a option }

let f t x = 
  t.x <- Some x

let g = f { x = None }
Run Code Online (Sandbox Code Playgroud)

在这里,尽管 确实g是一个函数,但限制的工作方式与上面的第一个示例完全相同:每次调用都g尝试对相同的泛型值进行操作T<'a>


在一些更简单的情况下,编译器可以采取捷径。因此,例如仅此行无法编译:

let f = List.map id
Run Code Online (Sandbox Code Playgroud)

但这两行确实:

let f = List.map id
let x = f [1;2;3]
Run Code Online (Sandbox Code Playgroud)

这是因为第二行允许编译器推断出f : list int -> list int,因此泛型参数消失了,每个人都很高兴。

实践证明,这条捷径涵盖了绝大多数情况。唯一真正遇到值限制的时候是当您尝试从模块导出此类通用值时。


在 Haskell 中,这整个情况不会发生,因为 Haskell 不承认突变。就那么简单。

但话又说回来,即使 Haskell 不承认突变,它也有点承认——通过unsafePerformIO。你猜怎么着——在这种情况下,你确实有遇到同样问题的风险。文档中甚至提到了这一点。

但 GHC 不会拒绝编译它 - 毕竟,如果您正在使用unsafePerformIO,您必须知道自己在做什么。正确的?:-)