有状态库顶部的无副作用界面


16

约翰·休斯(John Hughes)采访中,他谈到了Erlang和Haskell,他对在Erlang中使用有状态的库有以下看法:

如果要使用有状态的库,通常会在其之上构建无副作用的接口,以便随后可以在其余代码中安全地使用它。

他是什么意思?我试图考虑一个例子,但是我的想象力和/或知识使我失望。


好吧,如果状态存在,它就不会消失。诀窍是创建一些可以跟踪依赖项的东西。Haskell的标准答案是“ monads”或更高级的“ arrows”。他们很难缠住你的头,我从来没有真正做到过,所以其他人将不得不尝试解释它们。
Jan Hudec

Answers:


12

(我不认识Erlang,也不会写Haskell,但是我仍然可以回答)

好吧,在那次采访中,给出了一个随机数生成库的示例。这是一个可能的有状态接口:

# create a new RNG
var rng = RNG(seed)

# every time we call the next(ceil) method, we get a new random number
print rng.next(10)
print rng.next(10)
print rng.next(10)

输出可能是5 2 7。对于喜欢不变性的人来说,这是完全错误的!应该是5 5 5,因为我们在同一个对象上调用了方法。

那么无状态接口是什么呢?我们可以将随机数序列视为延迟计算的列表,next实际在其中检索头部:

let rng = RNG(seed)
let n : rng = rng in
  print n
  let n : rng = rng in
    print n
    let n : rng in
      print n

有了这样的界面,我们总是可以恢复到以前的状态。如果您的代码的两个引用相同的RNG,则它们实际上将获得相同的数字序列。在功能上,这是非常理想的。

以有状态的语言实现此操作并不复杂。例如:

import scala.util.Random
import scala.collection.immutable.LinearSeq

class StatelessRNG (private val statefulRNG: Random, bound: Int) extends LinearSeq[Int] {
  private lazy val next = (statefulRNG.nextInt(bound), new StatelessRNG(statefulRNG, bound))

  // the rest is just there to satisfy the LinearSeq trait
  override def head = next._1
  override def tail = next._2
  override def isEmpty = false
  override def apply(i: Int): Int = throw new UnsupportedOperationException()
  override def length = throw new UnsupportedOperationException()
}

// print out three nums
val rng = new StatelessRNG(new Random(), 10)
rng.take(3) foreach (n => println(n))

一旦添加了一些语法糖,使其看起来像一个列表,这实际上是非常不错的。


1
对于第一个示例:rnd.next(10)每次生成不同的值与不变性的关系不像对函数的定义那样多:函数必须是一对一的。(不过+1,好东西)
史蒂文·埃弗斯

谢谢!那是一个非常好的,清晰的解释和例子。
2013年

1

这里的关键概念是外部可变状态。没有外部可变状态的库是没有副作用的库。这样的库中的每个函数仅取决于传递给它的参数。

  • 如果您的函数访问的不是它创建的任何资源(即作为参数),那么它取决于外部状态
  • 如果您的函数创建了一些不会传递给调用方(也不会销毁它)的东西,那么您的函数正在创建外部状态。
  • 当上面的外部状态在不同时间可以具有不同的值时,则它是可变的

我使用的方便的石蕊测试:

  • 如果需要在功能B之前运行功能A,则A创建B所依赖的外部状态。
  • 如果我正在编写的函数无法记忆,则取决于外部可变状态。(由于内存压力,记住可能不是一个好主意,但仍然可以实现)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.