Interfaccia senza effetti collaterali in cima a una libreria stateful


16

In un'intervista con John Hughes in cui parla di Erlang e Haskell, ha il seguente da dire sull'uso delle biblioteche con stato in Erlang:

Se voglio usare una libreria stateful, di solito costruisco un'interfaccia priva di effetti collaterali in modo da poterla usare in modo sicuro nel resto del mio codice.

Cosa intende con questo? Sto cercando di pensare a un esempio di come questo apparirebbe, ma la mia immaginazione e / o conoscenza mi sta venendo meno.


Bene, se lo stato esiste, non andrà via. Il trucco è creare qualcosa che tenga traccia della dipendenza. La risposta standard di Haskell è "monadi" o le "frecce" più avanzate . Sono un po 'difficili da avvolgere la testa e non l'ho mai fatto davvero, quindi qualcun altro dovrebbe provare a spiegarli.
Jan Hudec,

Risposte:


12

(Non conosco Erlang e non posso scrivere Haskell, ma penso di poter rispondere comunque)

Bene, in quella intervista viene fornito l'esempio di una biblioteca di generazione di numeri casuali. Ecco una possibile interfaccia stateful:

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

L'output potrebbe essere 5 2 7. Per qualcuno a cui piace l'immutabilità, questo è chiaramente sbagliato! Dovrebbe essere 5 5 5, perché abbiamo chiamato il metodo sullo stesso oggetto.

Quindi quale sarebbe un'interfaccia senza stato? Possiamo vedere la sequenza di numeri casuali come un elenco valutato pigramente, dove nexteffettivamente recupera la testa:

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

Con una tale interfaccia, possiamo sempre tornare a uno stato precedente. Se due parti del codice si riferiscono allo stesso RNG, otterranno effettivamente la stessa sequenza di numeri. In una mentalità funzionale, questo è altamente desiderabile.

L'implementazione in un linguaggio di stato non è poi così complicata. Per esempio:

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

Una volta aggiunto un po 'di zucchero sintattico in modo che sembri un elenco, questo in realtà è abbastanza bello.


1
Come per il tuo primo esempio: rnd.next (10) che produce valori diversi ogni volta non ha a che fare con l'immutabilità tanto quanto ha a che fare con la definizione di una funzione: le funzioni devono essere 1 a 1. (+1 però, roba buona)
Steven Evers

Grazie! È stata una spiegazione ed un esempio davvero belli e chiari.
beta

1

Un concetto chiave qui è quello dello stato mutabile esterno . Una libreria che non ha uno stato mutabile esterno è priva di effetti collaterali. Ogni funzione in una tale biblioteca dipende solo dagli argomenti passati in essa.

  • Se la tua funzione accede a qualsiasi risorsa che non è stata creata da essa, assegnandole (cioè come parametro), allora dipende dallo stato esterno .
  • Se la tua funzione crea qualcosa che non passa al chiamante (e non la distrugge), allora la tua funzione sta creando uno stato esterno.
  • Quando lo stato esterno dall'alto può avere valori diversi in momenti diversi, è mutabile .

Pratiche cartina di tornasole che utilizzo:

  • se la funzione A deve essere eseguita prima della funzione B, allora A crea uno stato esterno da cui B dipende.
  • se una funzione che sto scrivendo non può essere memorizzata, dipende dallo stato mutabile esterno. (La memoizzazione potrebbe non essere una buona idea a causa della pressione della memoria, ma dovrebbe essere ancora possibile)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.