Nebenwirkungsfreie Oberfläche auf einer Stateful Library

16

In einem Interview mit John Hughes, in dem er über Erlang und Haskell spricht, hat er Folgendes über die Verwendung von Stateful Libraries in Erlang zu sagen:

Wenn ich eine Stateful-Bibliothek verwenden möchte, erstelle ich normalerweise eine Schnittstelle ohne Nebeneffekte, damit ich sie im restlichen Code sicher verwenden kann.

Was meint er damit? Ich versuche mir ein Beispiel zu überlegen, wie das aussehen würde, aber meine Vorstellungskraft und / oder mein Wissen scheitern.

Beta
quelle
Nun, wenn der Staat existiert, wird er nicht verschwinden. Der Trick besteht darin, etwas zu erstellen, das die Abhängigkeit verfolgt. Die Standard-Haskell-Antwort lautet "Monaden" oder die fortgeschritteneren "Pfeile" . Es ist ein bisschen schwierig, den Kopf herumzureißen, und ich habe es nie wirklich getan, also müsste jemand anderes versuchen, sie zu erklären.
Jan Hudec

Antworten:

12

(Ich kenne Erlang nicht und ich kann Haskell nicht schreiben, aber ich denke, ich kann trotzdem antworten.)

Nun, in diesem Interview wird das Beispiel einer Bibliothek zur Erzeugung von Zufallszahlen gegeben. Hier ist eine mögliche Stateful-Schnittstelle:

# 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)

Ausgabe kann sein 5 2 7. Für jemanden, der Unveränderlichkeit mag, ist das einfach falsch! Es sollte sein 5 5 5, weil wir die Methode für dasselbe Objekt aufgerufen haben.

Was wäre eine zustandslose Schnittstelle? Wir können die Folge von Zufallszahlen als träge bewertete Liste anzeigen, in nextder der Kopf tatsächlich abgerufen wird:

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

Mit einer solchen Schnittstelle können wir immer zu einem früheren Zustand zurückkehren. Wenn zwei Teile Ihres Codes auf dasselbe RNG verweisen, erhalten sie tatsächlich dieselbe Zahlenfolge. In einer funktionalen Denkweise ist dies sehr wünschenswert.

Dies in einer Stateful-Sprache umzusetzen ist nicht so kompliziert. Beispielsweise:

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))

Wenn Sie ein bisschen syntaktischen Zucker hinzugefügt haben, damit er sich wie eine Liste anfühlt, ist das eigentlich ganz nett.

amon
quelle
1
Was Ihr erstes Beispiel betrifft: rnd.next (10), das jedes Mal andere Werte erzeugt, hat nicht so sehr mit Unveränderlichkeit zu tun wie mit der Definition einer Funktion: Funktionen müssen 1-zu-1 sein. (+1 obwohl, gute Sachen)
Steven Evers
Vielen Dank! Das war eine wirklich schöne, übersichtliche Erklärung und ein gutes Beispiel.
Beta
1

Ein Schlüsselbegriff ist hier der des externen veränderlichen Zustands . Eine Bibliothek, die keinen externen veränderlichen Zustand aufweist, ist eine Bibliothek, die frei von Nebenwirkungen ist. Jede Funktion in einer solchen Bibliothek hängt nur von den übergebenen Argumenten ab.

  • Wenn Ihre Funktion auf eine Ressource zugreift, die sie nicht erstellt hat (dh als Parameter), hängt dies vom externen Status ab .
  • Wenn Ihre Funktion etwas erstellt, das nicht an den Aufrufer zurückgegeben (und nicht zerstört) wird, erstellt Ihre Funktion einen externen Status.
  • Wenn der externe Zustand von oben zu verschiedenen Zeiten unterschiedliche Werte haben kann, ist er veränderlich .

Handliche Lackmustests, die ich benutze:

  • Wenn die Funktion A vor der Funktion B ausgeführt werden muss, erstellt A einen externen Zustand, von dem B abhängt.
  • Wenn eine Funktion, die ich schreibe, nicht gespeichert werden kann, hängt dies vom externen veränderlichen Status ab. (Das Auswendiglernen ist möglicherweise aufgrund des Speicherdrucks keine gute Idee, sollte aber dennoch möglich sein.)
Steven Evers
quelle