Estou trabalhando em Escreva para você mesmo um esquema em 48 horas (até cerca de 85 horas) e cheguei à parte sobre como adicionar variáveis e atribuições . Há um grande salto conceitual neste capítulo, e eu gostaria que tivesse sido feito em duas etapas, com uma boa refatoração no meio, em vez de pular direto para a solução final. De qualquer forma…
Eu me perdi com um número de diferentes classes que parecem servir o mesmo propósito: State
, ST
, IORef
, eMVar
. Os três primeiros são mencionados no texto, enquanto o último parece ser a resposta favorita para muitas perguntas do StackOverflow sobre os três primeiros. Todos eles parecem carregar um estado entre invocações consecutivas.
O que são cada um deles e como eles diferem um do outro?
Em particular, essas frases não fazem sentido:
Em vez disso, usamos um recurso chamado threads de estado , permitindo que Haskell gerencie o estado agregado para nós. Isso nos permite tratar variáveis mutáveis como faríamos em qualquer outra linguagem de programação, usando funções para obter ou definir variáveis.
e
O módulo IORef permite usar variáveis com estado dentro da mônada IO .
Tudo isso torna a linha type ENV = IORef [(String, IORef LispVal)]
confusa - por que a segunda IORef
? O que vai quebrar se eu escrever em type ENV = State [(String, LispVal)]
vez disso?
MVar
deve ser preferidoSTRef
?STRef
garante que apenas um thread pode sofrer mutação (e que nenhum outro tipo de IO pode ocorrer) - certamente isso é melhor se eu não precisar de acesso simultâneo ao estado mutável?Ok, vou começar com
IORef
.IORef
fornece um valor que é mutável na mônada IO. É apenas uma referência a alguns dados e, como qualquer referência, existem funções que permitem alterar os dados a que se referem. Em Haskell, todas essas funções operam emIO
. Você pode pensar nele como um banco de dados, arquivo ou outro armazenamento de dados externo - você pode obter e definir os dados nele, mas para fazer isso, é necessário passar pelo IO. A razão pela qual IO é necessário é porque Haskell é puro ; o compilador precisa de uma maneira de saber para quais dados a referência aponta a qualquer momento (leia postagem do blog "Você poderia ter inventado mônadas" da sigfpe).MVar
s são basicamente a mesma coisa que um IORef, exceto por duas diferenças muito importantes.MVar
é uma primitiva de simultaneidade, portanto, foi projetada para acessar vários threads. A segunda diferença é queMVar
é uma caixa que pode estar cheia ou vazia. Portanto, onde umIORef Int
sempre tem umInt
(ou é inferior), umMVar Int
pode ter umInt
ou pode estar vazio. Se um thread tentar ler um valor de um vazioMVar
, ele bloqueará até queMVar
seja preenchido (por outro thread). Basicamente, umMVar a
é equivalente a umIORef (Maybe a)
com semântica extra que é útil para simultaneidade.State
é uma mônada que fornece estado mutável, não necessariamente com IO. Na verdade, é particularmente útil para cálculos puros. Se você tiver um algoritmo que usa estado, mas nãoIO
, umState
mônada costuma ser uma solução elegante.Há também uma versão do transformador de mônada do Estado
StateT
,. Freqüentemente, é usado para armazenar dados de configuração de programa ou tipos de estado de "estado do mundo do jogo" em aplicativos.ST
é algo ligeiramente diferente. A principal estrutura de dados emST
é oSTRef
, que é como um,IORef
mas com uma mônada diferente. AST
mônada usa truques do sistema de tipo (os "threads de estado" que os documentos mencionam) para garantir que os dados mutáveis não escapem da mônada; ou seja, quando você executa um cálculo de ST, obtém um resultado puro. O motivo pelo qual ST é interessante é que ele é uma mônada primitiva como IO, permitindo cálculos para realizar manipulações de baixo nível em bytearrays e ponteiros. Isso significa queST
pode fornecer uma interface pura enquanto usa operações de baixo nível em dados mutáveis, o que significa que é muito rápido. Da perspectiva do programa, é como se oST
cálculo fosse executado em um thread separado com armazenamento local do thread.fonte
Outros fizeram as coisas essenciais, mas para responder à pergunta direta:
Lisp é uma linguagem funcional com estado mutável e escopo lexical. Imagine que você fechou em uma variável mutável. Agora você tem uma referência a essa variável dentro de alguma outra função - digamos (em pseudocódigo no estilo haskell)
(printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y)
. Agora você tem duas funções - uma imprime xe outra define seu valor. Ao avaliarprintIt
, você deseja pesquisar o nome de x no ambiente inicial em queprintIt
foi definido, mas deseja pesquisar o valor ao qual o nome está vinculado no ambiente em queprintIt
é chamado (apóssetIt
pode ter sido chamado várias vezes )Existem maneiras além dos dois IORefs de fazer isso, mas você certamente precisa de mais do que o último tipo que você propôs, o que não permite alterar os valores aos quais os nomes estão vinculados de uma forma com escopo léxico. Pesquise no Google o "problema do funargs" para obter uma porção de pré-história interessante.
fonte