Digamos que eu tenha que implementar uma função
f :: Foo -> ReaderT Bar IO Baz
que tenho que passar para um consumidor (ou seja, vou chamar ) c f
onde Foo
// são impostos o consumidor da função, e que o consumidor chamará essa função repetidamente com s diferentes.Bar
Baz
Foo
Tenho alguma chance de manter algum estado local entre chamadas sucessivas para f
?
De alguma forma, já consegui isso através de um IORef
, alterando a assinatura def
f :: IORef S -> Foo -> ReaderT Client IO a
e aplicá-lo parcialmente a algum estado que eu inicializei de antemão:
s <- initState :: IO (IORef S)
c $ f s
Dessa forma, o totalmente aplicado f
pode alterar o s
tate na IO
mônada via atomicModifyIORef'
e alguma u
função pdate:
f s x = do
liftIO $ atomicModifyIORef' s u
-- ...
No entanto, a solução acima foi natural para mim porque esse s
tate é realmente um estado mutável global que é modificado não apenas por chamadas para f
, mas também por alguma outra parte do programa que é executada simultaneamente com f
.
Mas agora preciso de f
algum estado privado que não deva ser modificado por mais ninguém. Isso me faz pensar no StateT
transformador, mas não sei se posso aplicá-lo neste caso.
O caso prático em questão é que desejo uma notify
função com estado no contexto da implementação de um servidor de notificação. Veja aqui um exemplo de brinquedo da implementação. Neste cenário, Foo -> ReaderT Bar IO Baz
é na verdade MethodCall -> ReaderT Client IO Reply
( ReaderT Client IO a
is DBusR a
).
Como você pode ver, o que quer que eu faça com notify
, devo acabar passando uma MethodCall -> DBusR Reply
função para export
, client
e essa função será chamada várias vezes e espera-se que retorne sempre, então me parece que a única maneira de manter o estado nela é o seu fechamento, ou seja, tenho que fornecer notify
mais um argumento antes MethodCall
e aplicá-lo parcialmente ao estado inicial, como expliquei acima. E a única maneira de fazer com que esse estado mude toda vez que a MethodCall
for passado é fazer com que o primeiro argumento adicional seja um estado verdadeiramente mutável, por exemplo, o que IORef
mencionei acima.
É isso?
Imagine que a assinatura de
f
fosse:e você de alguma forma "usou
StateT
"f
para encadear um estado local por meio de uma sequência de chamadas paraf
. Isso significaria que duas chamadas consecutivasf
com o mesmoFoo
argumento com o leitor resultante seriam executadas usando o mesmoBar
argumento:poderia resultar em dois resultados diferentes
baz1
,baz2
dependendo da mudança no estado local, certo? Em outras palavras, seria uma violação da transparência referencial.Então, a única razão pela qual você pode realizar um estado
f :: Foo -> ReaderT Bar IO Baz
é porque você tem sua mônada baseIO
disponível, então qualquer solução que você encontrar terá que usarIO
efeitos para manter o estado.No entanto, é fácil de usar
StateT
dentro da definição def
, ao mesmo tempo em que mantém o estado comoIORef
algo privado paraf
. Se você escrever suaf
lógica principal usandoStateT
:você pode construir uma espécie de
f
fábrica:e em IO, você pode escrever:
Observe que, como
sref
foi criado emf_factory
, ele é incluído no encerramento resultante,f_with_state
mas não está disponível em nenhum outro lugar. E, na definição principal def
, você pode apenas usarget
,,, etc., sem se preocupar com o fato de que a função real está restaurando e salvandoput
o estado no IORef privado.modify
f_with_state
sref