【问题标题】:Understanding F# value restriction了解 F# 值限制
【发布时间】:2020-07-29 06:46:22
【问题描述】:

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

这是我正在学习的书中的例子。

let mapFirst = List.map fst

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

let mapFirst inp = List.map fst inp   // or inp |> List.map fst

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

val mapFirst : ('a * 'b) list -> 'a list

如果我提醒正确的话,我在haskell中调用了这个东西eta-conversion,上面两个例子完全一样。 (但也许不是完全)。为什么我要显式地为函数提供参数而不丢失任何信息?

我已经理解了类似的东西

let empties = Array.create 100 []

不会编译以及为什么,但我认为这与我的问题无关。

※我看了this question,但没有用。

【问题讨论】:

  • 我在let mapFirst = List.map fst 上没有得到 FS0030。你在函数中还做了什么,你怎么称呼它?也许这与 FSI 特别有关?
  • @Luaan 我只是在 fsi 中直接输入了let mapFirst = List.map fst;;

标签: f#


【解决方案1】:

这与可变性有关。

考虑一下这个sn-p:

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

let t = { x = None }

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

然后,假设您在程序的某个部分执行:

t.x <- Some 42

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

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

t.x <- Some "foo"

哦,不,现在会发生什么?是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 }

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


在一些更简单的情况下,编译器可以走捷径。因此,例如,仅此行不能编译:

let f = List.map id

但是这两行可以:

let f = List.map id
let x = f [1;2;3]

这是因为第二行允许编译器推断出f : list int -&gt; list int,所以泛型参数消失了,大家都很开心。

在实践中,这个快捷方式涵盖了绝大多数情况。您真正唯一遇到值限制的情况是当您尝试从模块中导出此类通用值时。


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

但是话又说回来,尽管 Haskell 不承认突变,但它有点承认 - 通过unsafePerformIO。你猜怎么着——在那种情况下,你确实有遇到同样问题的风险。甚至是mentioned in the documentation

除了 GHC 不拒绝编译它——毕竟,如果你使用unsafePerformIO,你必须知道你在做什么。正确的? :-)

【讨论】:

  • 感谢您的回答,但我已经通过我的问题中的empties 示例了解了这一点。我无法理解mapFirst 示例和您的示例之间有什么事情要做。 mapFirst 不应该是没有参数的泛型吗?我没有看到值 mapFirst... 的任何突变
  • 正如我在答案中已经解释的那样,编译器无法证明某处没有突变,所以它采取了安全路线。
  • 我刚刚更新了答案,添加了部分应用函数的插图。
  • 啊,仔细琢磨了一下你的详细回答,终于得出一个结论:F#编译器不接受泛型value,因为后面可能会破坏类型推断,所以我们必须显式地为编译器提供参数以将值确定为函数。这就是为什么它是 value 限制。我理解正确吗?
  • 是的,或多或少
猜你喜欢
  • 2010-11-11
  • 2011-05-03
  • 2013-01-26
  • 2015-06-15
  • 1970-01-01
  • 1970-01-01
  • 2012-03-13
相关资源
最近更新 更多