16

这些天我正在学习 Scala。我对 Haskell 有一点熟悉,尽管我不能声称自己很了解它。

给不熟悉 Haskell 的人的附注

我喜欢 Haskell 的一个特点是,不仅函数是一等公民,而且副作用(让我称之为动作)也是一等公民。一个动作,在执行时会给你一个 type 的值a,属于一个特定的 type IO a。您可以像传递任何其他值一样传递这些操作,并以有趣的方式将它们组合起来。

事实上,结合副作用是 Haskell 中对它们做某事的唯一方法,因为你无法执行它们。相反,main执行的程序是您的函数返回的组合操作。这是一个巧妙的技巧,它可以让函数变得纯粹,同时让你的程序实际上做一些除了消耗能量之外的事情。

这种方法的主要优点是编译器知道您执行副作用的代码部分,因此它可以帮助您捕获错误。

实际问题

在 Scala 中是否有某种方法可以让编译器类型为您检查副作用,例如,您可以保证不会在某个函数内执行副作用?

4

2 回答 2

23

不,这在 Scala 中原则上是不可能的,因为该语言不强制引用透明性——语言语义忽略了副作用。您的编译器不会为您跟踪和强制摆脱副作用。

您将能够使用类型系统将某些操作标记IO类型,并且通过程序员的纪律,获得一些编译器支持,但没有编译器证明。

于 2012-07-23T21:00:57.673 回答
18

强制引用透明性的能力与 Scala 拥有与 Java 可互操作的类/对象系统的目标非常不兼容。

Java 代码可以以任意方式不纯(并且在 Scala 编译器运行时可能无法用于分析),因此 Scala 编译器必须假定所有外部代码都是不纯的(为它们分配IO类型)。要使用对 Java 的调用来实现纯 Scala 代码,您必须将调用包装在等效于unsafePerformIO. 这增加了样板文件并使互操作性变得不那么愉快,但它变得更糟。

除非程序员另有承诺,否则必须假设所有 Java 代码都在IO其中,这几乎会扼杀从 Java 类的继承。所有继承的方法都必须假设在IO类型中;这甚至适用于接口,因为 Scala 编译器必须假设在 Java 领域的某个地方存在不纯的实现。因此,您永远不能IO从 Java 类或接口派生带有任何非方法的 Scala 类。

更糟糕的是,即使是在 Scala 中定义的类,理论上也可能在 Java 中定义一个未跟踪的子类,其中包含不纯的方法,其实例可能会作为父类的实例传回 Scala。因此,除非 Scala 编译器可以证明给定对象不可能是 Java 代码定义的类的实例,否则它必须假定对该对象的任何方法调用都可能调用由 Java 编译器编译的代码,而不遵守返回结果不在的函数IO可以做什么。这将迫使几乎所有东西都IO. 但是把所有东西都IO放进去就等于什么都不放进去IO,只是不跟踪副作用!

所以最终,Scala鼓励你编写纯代码,但它并没有试图强制你这样做。就编译器而言,任何对任何东西的调用都会产生副作用。

于 2012-07-24T01:19:57.250 回答