Interfaccia senza effetti collaterali in cima a una libreria stateful

16

In un'intervista intervista a John Hughes dove parla di Erlang e Haskell, ha il a seguire sull'utilizzo di librerie stateful in Erlang:

If I want to use a stateful library, I usually build a side effect-free interface on top of it so that I can then use it safely in the rest of my code.

Che cosa intende con questo? Sto cercando di pensare ad un esempio di come questo sembrerebbe, ma la mia immaginazione e / o conoscenza mi stanno deludendo.

    
posta beta 07.11.2013 - 14:49
fonte

2 risposte

12

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

Bene, in quell'intervista viene fornito l'esempio di una libreria di generazione di numeri casuali. Ecco una possibile interfaccia di stato:

# 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 . A qualcuno a cui piace l'immutabilità, questo è assolutamente sbagliato! Dovrebbe essere 5 5 5 , perché abbiamo chiamato il metodo sullo stesso oggetto.

Quindi cosa sarebbe un'interfaccia stateless? Possiamo visualizzare la sequenza di numeri casuali come un elenco valutato pigramente, in cui next recupera effettivamente 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 tuo codice si riferiscono allo stesso RNG, avranno effettivamente la stessa sequenza di numeri. In una mentalità funzionale, questo è altamente auspicabile.

L'implementazione di questo in un linguaggio di stato non è così complicata. Ad 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, in realtà è piuttosto carino.

    
risposta data 07.11.2013 - 18:33
fonte
1

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

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

Prove di tornasole pratici che uso:

  • se la funzione A deve essere eseguita prima della funzione B allora A crea uno stato esterno di cui B dipende.
  • se una funzione che sto scrivendo non può essere memoizzata, allora dipende dallo stato mutabile esterno. (La memoizzazione potrebbe non essere una buona idea a causa della pressione della memoria, ma dovrebbe essere ancora possibile)
risposta data 07.11.2013 - 21:55
fonte

Leggi altre domande sui tag