2012-09-15 11 views
9

Estoy leyendo acerca de la base matemática detrás de Haskell: he aprendido cómo los cierres se pueden usar para guardar el estado en una función.¿Cómo funcionan los cierres en Haskell?

Me preguntaba si Haskell permite cierres y cómo funcionan porque no son funciones puras.

Si una función modifica su estado de cierre, podrá dar salidas diferentes en entradas idénticas.

¿Cómo no es un problema en Haskell? ¿Es porque no puede reasignar una variable después de asignarle inicialmente un valor?

+6

Simplemente, no puede modificar el estado cerrado o cualquier otro :) – is7s

+1

Pregunta similar: http://stackoverflow.com/questions/9419175/are-closures-a-violation-of-the-functional- programming-paradigm/ – amindfv

+0

@ is7s pero puede hacer que se cree una instancia más entre llamadas, si se trata de datos no atómicos. –

Respuesta

8

El cierre simplemente "agrega" variables adicionales para funcionar, por lo que no hay nada más que pueda hacer con ellas que con las "normales", es decir, sin modificar el estado.

Leer más: Closures (in Haskell)

+0

Ok gracias, aceptando esta respuesta para el lenguaje de intuición para principiantes – nidoran

+0

¿Haskell considera que las variables cerradas son valores o referencias? – CMCDragonkai

+0

Creo que primero tendrías que explicar a qué te refieres cuando dices 'referencia', ya que puede llevar a confusión dado que estamos en un mundo Haskell puro. – Bartosz

10

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.

+1

Gracias por la respuesta, pero la mayor parte de esto está por encima de mi cabeza por el momento. Volveré a esto cuando sepa más – nidoran

1

Como han dicho otros, Haskell no permite que se altere el "estado" de un cierre. Esto le impide hacer cualquier cosa que pueda romper la pureza de la función.

+0

¿Eso significa que si declaro una variable que está cerrada por una función, si luego modifico esa variable fuera de esa función y luego ejecuto esa función, esa función ignorará mi mutación y aún devolver el mismo resultado que cuando no muteé la variable cerrada? – CMCDragonkai

+1

Haskell no le permite "modificar" una variable más adelante. (O nunca) – MathematicalOrchid