En realidad se puede simular el cierre en Haskell, pero no de la manera que usted podría pensar. En primer lugar, voy a definir un tipo de cierre:
data Closure i o = Respond (i -> (o, Closure i o))
Esta define un tipo que en cada "paso" toma un valor de tipo i
que se utiliza para calcular una respuesta de tipo o
.
Por lo tanto, vamos a definir un "cierre" que acepta entradas y respuestas con números enteros vacíos, es decir .:
incrementer :: Closure() Int
comportamiento de este cierre variará de solicitud para pedir. Voy a mantenerlo simple y hacerlo de modo que responde con 0 a la primera respuesta y luego incrementos de su respuesta para cada solicitud sucesiva:
incrementer = go 0 where
go n = Respond $ \() -> (n, go (n + 1))
Podemos entonces se le pregunta en repetidas ocasiones el cierre, lo que produce un resultado y una nuevo cierre:
query :: i -> Closure i o -> (o, Closure i o)
query i (Respond f) = f i
Observe que el segundo medio del tipo anterior se asemeja a un patrón común en Haskell, que es el State
mónada:
newtype State s a = State { runState :: s -> (a, s) }
Se puede importar desde Control.Monad.State
. Por lo tanto podemos concluir query
en este State
mónada:
query :: i -> State (Closure i o) o
query i = state $ \(Respond f) -> f i
... y ahora tenemos una manera genérica para consultar cualquier cierre mediante el State
mónada:
someQuery :: State (Closure() Int) (Int, Int)
someQuery = do
n1 <- query()
n2 <- query()
return (n1, n2)
Pasemos que nuestro cierre y ver lo que sucede:
>>> evalState someQuery incrementer
(0, 1)
Vamos a escribir un cierre diferente que devuelve algún patrón arbitrario:
weirdClosure :: Closure() Int
weirdClosure = Respond (\() -> (42, Respond (\() -> (666, weirdClosure))))
...y probarlo:
>>> evalState someQuery weirdClosure
(42, 666)
Ahora, escribir cierres a mano parece bastante incómodo. ¿No sería bueno si pudiéramos usar la notación do
para escribir el cierre? Bueno, podemos! Sólo tenemos que hacer un cambio a nuestro tipo de cierre:
data Closure i o r = Done r | Respond (i -> (o, Closure i o r))
Ahora podemos definir una instancia Monad
(de Control.Monad
) para Closure i o
:
instance Monad (Closure i o) where
return = Done
(Done r) >>= f = f r
(Respond k) >>= f = Respond $ \i -> let (o, c) = k i in (o, c >>= f)
Y podemos escribir una función de conveniencia que corresponde a servicio a una sola petición:
answer :: (i -> o) -> Closure i o()
answer f = Respond $ \i -> (f i, Done())
... cual podemos utilizar para reescribir todos los viejos cierres:
incrementer :: Closure() Int()
incrementer = forM_ [1..] $ \n -> answer (\() -> n)
weirdClosure :: Closure() Int r
weirdClosure = forever $ do
answer (\() -> 42)
answer (\() -> 666)
Ahora sólo cambiar nuestra función de consulta a:
query :: i -> StateT (Closure i o r) (Either r) o
query i = StateT $ \x -> case x of
Respond f -> Right (f i)
Done r -> Left r
... y lo utilizan para escribir consultas:
someQuery :: StateT (Closure() Int()) (Either()) (Int, Int)
someQuery = do
n1 <- query()
n2 <- query()
return (n1, n2)
Ahora probarlo!
>>> evalStateT someQuery incrementer
Right (1, 2)
>>> evalStateT someQuery weirdClosure
Right (42, 666)
>>> evalStateT someQuery (return())
Left()
Sin embargo, todavía no considero que un enfoque verdaderamente elegante, así que voy a concluir enchufando descaradamente mi tipo Proxy en mi pipes
como una forma mucho más general y más estructurado de cierres de escritura y su consumidores. El tipo Server
representa un cierre generalizado y el Client
representa un consumidor generalizado de un cierre.
Simplemente, no puede modificar el estado cerrado o cualquier otro :) – is7s
Pregunta similar: http://stackoverflow.com/questions/9419175/are-closures-a-violation-of-the-functional- programming-paradigm/ – amindfv
@ is7s pero puede hacer que se cree una instancia más entre llamadas, si se trata de datos no atómicos. –