Interfaz sin efectos secundarios en la parte superior de una biblioteca con estado

16

En una entrevista con John Hughes donde habla sobre Erlang y Haskell, tiene lo siguiente que decir sobre el uso de bibliotecas con estado en Erlang:

Si quiero usar una biblioteca con estado, generalmente construyo una interfaz libre de efectos secundarios encima para que luego pueda usarla de manera segura en el resto de mi código.

¿A qué se refiere con esto? Estoy tratando de pensar en un ejemplo de cómo se vería esto, pero mi imaginación y / o conocimiento me están fallando.

beta
fuente
Bueno, si el estado existe, no desaparecerá. El truco es crear algo que haga un seguimiento de la dependencia. La respuesta estándar de Haskell es "mónadas" o las "flechas" más avanzadas . Son un poco difíciles de entender y nunca lo hice realmente, por lo que alguien más tendría que tratar de explicarlos.
Jan Hudec

Respuestas:

12

(No conozco a Erlang, y no puedo escribir Haskell, pero creo que puedo responder)

Bueno, en esa entrevista se da el ejemplo de una biblioteca de generación de números aleatorios. Aquí hay una posible interfaz con estado:

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

La salida puede ser 5 2 7. Para alguien a quien le gusta la inmutabilidad, ¡esto es simplemente incorrecto! Debería serlo 5 5 5, porque llamamos al método en el mismo objeto.

Entonces, ¿cuál sería una interfaz sin estado? Podemos ver la secuencia de números aleatorios como una lista vagamente evaluada, donde nextrealmente recupera la cabeza:

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 dicha interfaz, siempre podemos volver a un estado anterior. Si dos partes de su código se refieren al mismo RNG, en realidad obtendrán la misma secuencia de números. En una mentalidad funcional, esto es altamente deseable.

Implementar esto en un lenguaje con estado no es tan complicado. Por ejemplo:

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 vez que agrega un poco de azúcar sintáctica para que parezca una lista, en realidad es bastante agradable.

amon
fuente
1
En cuanto a su primer ejemplo: rnd.next (10) producir valores diferentes cada vez no tiene que ver tanto con la inmutabilidad como con la definición de una función: las funciones deben ser 1 a 1. (+1 sin embargo, cosas buenas)
Steven Evers
¡Gracias! Esa fue una explicación y un ejemplo realmente agradables y llamativos.
beta
1

Un concepto clave aquí es el de estado mutable externo . Una biblioteca que no tiene un estado mutable externo, es una que está libre de efectos secundarios. Cada función en dicha biblioteca solo depende de los argumentos que se le pasan.

  • Si su función accede a cualquier recurso que no fue creado por él (dado como un parámetro), entonces depende del estado externo .
  • Si su función crea algo que no le devuelve al llamante (y no lo destruye), entonces su función está creando un estado externo.
  • Cuando el estado externo desde arriba puede tener diferentes valores en diferentes momentos, entonces es mutable .

Prácticas pruebas de tornasol que uso:

  • si la función A necesita ejecutarse antes de la función B, entonces A crea un estado externo del que depende B.
  • Si una función que estoy escribiendo no se puede memorizar, entonces depende del estado mutable externo. (La memorización podría no ser una buena idea debido a la presión de la memoria, pero aún así debería ser posible)
Steven Evers
fuente