Scala 编译器:检测 pure/impure 函数

Scala compiler: detecing a pure/impure function

在Scala、Haskell等FP语言中,使用纯函数使得编译器优化代码成为可能。例如:

val x = method1()// a pure function call
val y = method2// another pure function call
val c = method3(x,y)

由于 method1method2 是纯函数,因此计算相互独立,编译器可以并行化这两个调用。

像 Haskell 这样的语言内部有结构(如 IO monad),它告诉函数是纯函数还是执行一些 IO 操作。但是 Scala 编译器如何检测一个函数是纯函数呢?

将代码块分类为纯代码的一般方法是定义哪些操作是纯操作,并且由于纯操作是组合的,因此纯操作的组合是纯操作。

并行化实际上并不是纯代码更重要的好处之一:好处是可以使用任何评估策略。评估可以重新排序或结果可以缓存等。并行化是另一种评估策略,但没有很好地了解实际执行成本(请注意,现代 CPU 和内存层次结构很难获得这种感觉),它通常会减慢速度相对于其他策略而言。对于现代纯代码,懒惰和缓存重复值通常更普遍有效,而并行性由开发人员控制(纯代码的一个好处是您可以在不改变代码语义的情况下对并行化方式进行任意更改) .

在 Scala 的情况下,编译器不会真正努力对 pure/impure 代码进行分类,并且通常不会尝试其他评估策略:控制权留给程序员(该语言在一定程度上有所帮助,因为它具有按名称调用和 lazy).

JVM 的 JIT 编译器在决定它可以安全地内联和重新排序的内容时,可以并且确实对字节码执行一些纯度分析。这不是特定于 Scala 的,尽管最终局部变量(在 Scala 中又称为局部 vals 或 Java 中的 final 变量)启用了一些无法以其他方式执行的优化。 Java脚本运行时(对于 ScalaJS)可以(并且在实践中非常积极地做)同样执行该分析,就像 LLVM(对于 Scala Native)一样。

一般情况下,Purity Analysis相当于解决Halting Problem。换句话说,在一般情况下,不可能静态地决定一段代码是否是纯的。

在像 Haskell 这样的语言中,没有办法在 Haskell 中编写不纯的代码,因此纯度分析是微不足道的。这是一个简单的函数,它将 Haskell 程序作为参数并告诉您它是否正确:

isPureProgram :: a -> Bool
isPureProgram _ = True

注意,我在这里简化了一些事情:

  • unsafePerformIO 和朋友们允许您执行不安全的 I/O。通常假定您在使用这些功能时知道自己在做什么。
  • 异常是副作用。
  • 与流行的看法相反,IO monad 不允许您在 Haskell 中编写不纯的代码。 IO monad 所做的是编写一个 程序,其中 returns 一个 IO 操作列表,当 解释 运行时系统导致计算不纯。但是,Haskell 程序 生成 这些 IO 操作仍然是纯粹的 – 它是 interpreter 这是不纯的。但当然,最终结果是一样的:将执行不纯的计算。

但是,由于 Scala 本质上是一种不纯的语言,因此编译器不能像 Haskell 编译器那样依赖类似的限制,因此在一般情况下无法执行纯度分析。