这些天我正在学习Scala。我对Haskell略有熟悉,虽然我不能声称对它很了解。
那些不熟悉Haskell
的学生评论我喜欢Haskell的一个特点是,不仅功能是一等公民,而且副作用(让我称之为行动)也是如此。执行时将赋予您类型为a
的值的操作属于特定类型IO a
。您可以像其他任何值一样传递这些操作,并以有趣的方式组合它们。
事实上,组合副作用是Haskell用它们做某事的唯一方法,因为你无法执行它们。相反,将执行的程序是由main
函数返回的组合操作。这是一个巧妙的技巧,允许函数是纯粹的,同时让你的程序实际上做的不是消耗电力。
这种方法的主要优点是编译器能够识别执行副作用的代码部分,因此它可以帮助您捕获它们的错误。
实际问题
在Scala中是否有某种方法可以让编译器类型检查副作用,例如 - 保证不会在某个函数中执行副作用?
答案 0 :(得分:22)
不,这在Scala中原则上是不可能的,因为该语言不强制引用透明性 - 语言语义忽略了副作用。您的编译器不会跟踪并强制执行副作用。
您将能够使用类型系统tag some actions作为IO
类型,并且使用程序员规则,获得一些编译器支持,但没有编译器证明。
答案 1 :(得分:15)
强制执行引用透明性的能力与Scala的目标是非常不兼容,即使用可与Java互操作的类/对象系统。
Java代码可能以任意方式存在(并且在Scala编译器运行时可能无法进行分析)因此Scala编译器必须假设所有外部代码都是不纯的(为它们分配{ {1}}类型)。要通过调用Java实现纯Scala代码,您必须将调用包装在等效于IO
的内容中。这增加了样板并使互操作性变得不那么令人愉快,但它变得更糟。
必须假设所有Java代码都在unsafePerformIO
中,除非程序员承诺否则几乎会杀死从Java类继承。所有继承的方法都必须假定为IO
类型;这甚至适用于接口,因为Scala编译器必须假设在Java-land的某处存在不纯的实现。因此,您永远无法从Java类或接口派生任何非IO
方法的Scala类。
更糟糕的是,即使对于Scala中定义的类,理论上也可以在Java中使用不纯的方法定义未跟踪的子类,其实例可以作为父类的实例传递回Scala。因此,除非Scala编译器证明某个给定对象不可能是Java代码定义的类的实例,否则它必须假定对该对象的任何方法调用都可能调用由该代码编译的代码。 Java编译器不遵守返回IO
以外的结果的函数的规则。这会迫使所有几乎都在IO
。但是将所有内容放在IO
中完全等同于IO
中没有任何内容,只是没有跟踪副作用!
最终,Scala 鼓励你编写纯代码,但它并没有试图强制你这样做。就编译器而言,任何对任何事物的调用都可能产生副作用。