Interface sem efeitos colaterais no topo de uma biblioteca com estado


16

Em uma entrevista com John Hughes, onde ele fala sobre Erlang e Haskell, ele tem o seguinte a dizer sobre o uso de bibliotecas com estado em Erlang:

Se eu quiser usar uma biblioteca com estado, geralmente construo uma interface sem efeitos colaterais sobre ela, para que eu possa usá-la com segurança no restante do meu código.

O que ele quer dizer com isso? Estou tentando pensar em um exemplo de como isso seria, mas minha imaginação e / ou conhecimento estão me falhando.


Bem, se o estado existe, ele não desaparece. O truque é criar algo que acompanhará a dependência. A resposta padrão de Haskell é "mônadas" ou as "setas" mais avançadas . Eles são um pouco difíceis de entender e eu nunca o fiz, então alguém teria que tentar explicá-los.
Jan Hudec

Respostas:


12

(Não conheço Erlang e não sei escrever Haskell, mas acho que posso responder)

Bem, nessa entrevista, é dado o exemplo de uma biblioteca de geração de números aleatórios. Aqui está uma possível interface com 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)

A saída pode ser 5 2 7. Para alguém que gosta de imutabilidade, isso é totalmente errado! Deveria ser 5 5 5, porque chamamos o método no mesmo objeto.

Então, o que seria uma interface sem estado? Podemos ver a sequência de números aleatórios como uma lista avaliada preguiçosamente, onde nextrealmente recupera a cabeça:

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

Com essa interface, sempre podemos reverter para um estado anterior. Se duas partes do seu código se referirem ao mesmo RNG, elas realmente obterão a mesma sequência de números. Em uma mentalidade funcional, isso é altamente desejável.

Implementar isso em uma linguagem stateful não é tão complicado. Por exemplo:

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

Depois de adicionar um pouco de açúcar sintático para parecer uma lista, isso é realmente muito bom.


1
Quanto ao seu primeiro exemplo: rnd.next (10) produzir valores diferentes a cada vez não tem a ver com imutabilidade, mas com a definição de uma função: as funções devem ser de 1 para 1. (+1 embora, coisas boas)
Steven Evers

Obrigado! Essa foi uma explicação e um exemplo realmente agradáveis ​​e perspicazes.
beta

1

Um conceito-chave aqui é o do estado mutável externo . Uma biblioteca que não possui um estado mutável externo é livre de efeitos colaterais. Toda função em uma biblioteca depende apenas dos argumentos passados ​​para ela.

  • Se sua função acessa qualquer recurso que não foi criado por ela, dado a ela (por exemplo, como parâmetro), isso depende do estado externo .
  • Se sua função cria algo que não é transmitido ao chamador (e não a destrói), sua função está criando um estado externo.
  • Quando o estado externo de cima pode ter valores diferentes em momentos diferentes, é mutável .

Testes de torneira úteis que eu uso:

  • se a função A precisar ser executada antes da função B, A criará um estado externo do qual B depende.
  • se uma função que estou escrevendo não pode ser memorizada, isso depende do estado mutável externo. (A memorização pode não ser uma boa ideia devido à pressão da memória, mas ainda deve ser possível)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.