Esta é uma "interpretação" sugerida da IO
mônada. Se você deseja levar essa "interpretação" a sério, precisa levar o "RealWorld" a sério. É irrelevante se action world
avaliado especulativamente ou não, action
não tem efeitos colaterais, seus efeitos, se houver, são tratados retornando um novo estado do universo onde esses efeitos ocorreram, por exemplo, um pacote de rede foi enviado. No entanto, o resultado da função é ((),world)
e, portanto, o novo estado do universo é world
. Não usamos o novo universo que podemos ter avaliado especulativamente ao lado. O estado do universo é world
.
Você provavelmente tem dificuldade em levar isso a sério. Há muitas maneiras pelas quais isso é superficialmente paradoxal e sem sentido. A simultaneidade é especialmente não óbvia ou louca com essa perspectiva.
"Espere, espere", você diz. " RealWorld
é apenas um 'sinal'. Na verdade, não é o estado de todo o universo." Ok, então essa "interpretação" não explica nada. No entanto, como um detalhe de implementação , é assim que o GHC modela IO
. 1 No entanto, isso significa que temos "funções" mágicas que realmente têm efeitos colaterais e esse modelo não fornece orientação para seu significado. E, como essas funções realmente têm efeitos colaterais, a preocupação que você levanta é completamente relevante. O GHC precisa se esforçar para garantir que RealWorld
essas funções especiais não sejam otimizadas de maneira a alterar o comportamento pretendido do programa.
Pessoalmente (como provavelmente já é evidente agora), acho que esse modelo de "passagem pelo mundo" IO
é apenas inútil e confuso como ferramenta pedagógica. (Se é útil para implementação, eu não sei. Para o GHC, acho que é mais um artefato histórico.)
Uma abordagem alternativa é exibir IO
como uma descrição de solicitações com manipuladores de resposta. Existem várias maneiras de fazer isso. Provavelmente o mais acessível é usar uma construção de mônada gratuita, especificamente podemos usar:
data IO a = Return a | Request OSRequest (OSResponse -> IO a)
Existem muitas maneiras de tornar isso mais sofisticado e com propriedades um pouco melhores, mas isso já é uma melhoria. Não requer suposições filosóficas profundas sobre a natureza da realidade para entender. Tudo o que afirma é que IO
é um programa trivial Return
que nada faz além de retornar um valor ou é uma solicitação ao sistema operacional com um manipulador para a resposta. OSRequest
pode ser algo como:
data OSRequest = OpenFile FilePath | PutStr String | ...
Da mesma forma, OSResponse
pode ser algo como:
data OSResponse = Errno Int | OpenSucceeded Handle | ...
(Uma das melhorias que pode ser feita é tornar as coisas mais seguras, para que você saiba que não será atendido OpenSucceeded
por uma PutStr
solicitação.) Isso modela a IO
descrição de solicitações que são interpretadas por algum sistema (para a IO
mônada "real", isso é o próprio tempo de execução Haskell) e, talvez, esse sistema chame o manipulador que fornecemos com uma resposta. Isso, é claro, também não fornece nenhuma indicação de como uma solicitação como PutStr "hello world"
deve ser tratada, mas também não pretende. Torna explícito que isso está sendo delegado para algum outro sistema. Este modelo também é bastante preciso. Todos os programas de usuário em sistemas operacionais modernos precisam fazer solicitações ao sistema operacional para fazer qualquer coisa.
Este modelo fornece as intuições corretas. Por exemplo, muitos iniciantes veem coisas como o <-
operador como "desembrulhando" IO
ou têm (infelizmente reforçadas) as visões de que um IO String
, digamos, é um "contêiner" que "contém" String
s (e depois <-
as tira). Essa visão de solicitação-resposta torna essa perspectiva claramente errada. Não há identificador de arquivo dentro de OpenFile "foo" (\r -> ...)
. Uma analogia comum para enfatizar isso é que não há bolo dentro de uma receita para bolo (ou talvez "fatura" seria melhor nesse caso).
Esse modelo também funciona prontamente com simultaneidade. Podemos facilmente ter um construtor para OSRequest
curtir Fork :: (OSResponse -> IO ()) -> OSRequest
e, em seguida, o tempo de execução pode intercalar as solicitações produzidas por esse manipulador extra com o manipulador normal da maneira que desejar. Com alguma inteligência, você pode usar isso (ou técnicas relacionadas) para realmente modelar coisas como concorrência mais diretamente, em vez de apenas dizer "fazemos uma solicitação ao sistema operacional e as coisas acontecem". É assim que a IOSpec
biblioteca funciona.
1 O Hugs usou uma implementação baseada em continuação, IO
que é aproximadamente semelhante ao que eu descrevo, embora com funções opacas, em vez de um tipo de dados explícito. O HBC também usou uma implementação baseada em continuação em camadas sobre a antiga E / S baseada em fluxo de solicitação-resposta. O NHC (e, portanto, o YHC) usava thunks, ou seja, IO a = () -> a
apesar de o ()
nome ter sido chamado World
, mas não está passando o estado. O JHC e o UHC usaram basicamente a mesma abordagem que o GHC.