ステートフルライブラリ上の副作用のないインターフェイス


16

ジョン・ヒューズとのインタビュー彼はアーランとHaskellの語る、彼はErlangでステートフルなライブラリを使用することについて言うために、以下があります。

ステートフルライブラリを使用する場合は、通常、その上に副作用のないインターフェイスを構築し、残りのコードで安全に使用できるようにします。

これはどういう意味ですか?私はこれがどのように見えるかの例を考えようとしていますが、私の想像力や知識は私に失敗しています。


まあ、状態が存在する場合、それは消えません。トリックは、依存関係を追跡する何かを作成することです。Haskellの標準的な答えは、「モナド」またはより高度な「矢印」です。彼らはあなたの頭を包むのが少し難しいです、そして、私は本当にそうしなかったので、他の誰かがそれらを説明しようとするでしょう。
ジャン・ヒューデック

回答:


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

このようなインターフェイスを使用すると、いつでも以前の状態に戻すことができます。コードの2つの部分が同じ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対1でなければなりません。(しかし、良いもの)
スティーブンエバーズ

ありがとう!それは本当に素晴らしく、目立つ説明と例でした。
ベータ

1

ここでの重要な概念は外部の可変状態のものです。外部で変更可能な状態を持たないライブラリは、副作用がないライブラリです。このようなライブラリのすべての関数は、渡される引数にのみ依存します。

  • 関数が、それによって作成されていないリソースにアクセスした場合(つまり、パラメーターとして)、外部状態に依存します
  • 関数が呼び出し元に返さない(そして破棄しない)ものを作成する場合、関数は外部状態を作成しています。
  • 上記の外部状態が異なる時間に異なる値を持つことができる場合、それは可変です。

私が使用する便利なリトマス試験:

  • 関数Bの前に関数Aを実行する必要がある場合、AはBが依存する外部状態を作成します。
  • 私が書いている関数をメモできない場合、それは外部の可変状態に依存します。(メモ化はメモリのプレッシャーのために良いアイデアではないかもしれませんが、それはまだ可能であるべきです)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.