Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do closures work in Haskell?

Tags:

haskell

I'm reading about the math foundation behind Haskell - I've learned about how closures can be used to save state in a function.

I was wondering if Haskell allows closures, and how they work because they are not pure functions?

If a function modifies it's closed-over state it will be capable of giving different outputs on identical inputs.

How is this not a problem in Haskell? Is it because you can't reassign a variable after you initially assign it a value?

like image 497
nidoran Avatar asked Sep 15 '12 21:09

nidoran


People also ask

What are closures in Haskell?

From HaskellWiki. A closure, the opposite of a combinator, is a function that makes use of free variables in its definition. It 'closes' around some portion of its environment. for example. f x = (\y -> x + y)

Does Haskell use closures?

Closures are pervasive in Haskell. It is very common that lambda expressions capture variables from outside the body of the definition.

How do you use closures?

To use a closure, define a function inside another function and expose it. To expose a function, return it or pass it to another function. The inner function will have access to the variables in the outer function scope, even after the outer function has returned.

What do closures do?

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function.


2 Answers

You actually can simulate closures in Haskell, but not the way you might think. First, I will define a closure type:

data Closure i o = Respond (i -> (o, Closure i o ))

This defines a type that at each "step" takes a value of type i which is used to compute a response of type o.

So, let's define a "closure" that accepts empty inputs and answers with integers, i.e.:

incrementer :: Closure () Int

This closure's behavior will vary from request to request. I'll keep it simple and make it so that it responds with 0 to the first response and then increments its response for each successive request:

incrementer = go 0 where
    go n = Respond $ \() -> (n, go (n + 1))

We can then repeatedly query the closure, which yields a result and a new closure:

query :: i -> Closure i o -> (o, Closure i o)
query i (Respond f) = f i

Notice that the second half of the above type resembles a common pattern in Haskell, which is the State monad:

newtype State s a = State { runState :: s -> (a, s) }

It can be imported from Control.Monad.State. So we can wrap query in this State monad:

query :: i -> State (Closure i o) o
query i = state $ \(Respond f) -> f i

... and now we have a generic way to query any closure using the State monad:

someQuery :: State (Closure () Int) (Int, Int)
someQuery = do
    n1 <- query ()
    n2 <- query ()
    return (n1, n2)

Let's pass it our closure and see what happens:

>>> evalState someQuery incrementer
(0, 1)

Let's write a different closure that returns some arbitrary pattern:

weirdClosure :: Closure () Int
weirdClosure = Respond (\() -> (42, Respond (\() -> (666, weirdClosure))))

... and test it:

>>> evalState someQuery weirdClosure
(42, 666)

Now, writing closures by hand seems pretty awkward. Wouldn't it be nice if we could use do notation to write the closure? Well, we can! We only have to make one change to our closure type:

data Closure i o r = Done r | Respond (i -> (o, Closure i o r))

Now we can define a Monad instance (from Control.Monad) for 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)

And we can write a convenience function which corresponds to servicing a single request:

answer :: (i -> o) -> Closure i o ()
answer f = Respond $ \i -> (f i, Done ())

... which we can use to rewrite all our old closures:

incrementer :: Closure () Int ()
incrementer = forM_ [1..] $ \n -> answer (\() -> n)

weirdClosure :: Closure () Int r
weirdClosure = forever $ do
    answer (\() -> 42)
    answer (\() -> 666)

Now we just change our query function to:

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

... and use it to write queries:

someQuery :: StateT (Closure () Int ()) (Either ()) (Int, Int)
someQuery = do
    n1 <- query ()
    n2 <- query ()
    return (n1, n2)

Now test it!

>>> evalStateT someQuery incrementer
Right (1, 2)
>>> evalStateT someQuery weirdClosure
Right (42, 666)
>>> evalStateT someQuery (return ())
Left ()

However, I still don't consider that a truly elegant approach, so I'm going to conclude by shamelessly plugging my Proxy type in my pipes as a much general and more structured way of writing closures and their consumers. The Server type represents a generalized closure and the Client represents a generalized consumer of a closure.

like image 197
Gabriella Gonzalez Avatar answered Sep 19 '22 14:09

Gabriella Gonzalez


The closure just 'adds' additional variables to function, so there is nothing more you can do with them than you can with 'normal' ones, that is, certainly not modify the state.

Read more: Closures (in Haskell)

like image 41
Bartosz Avatar answered Sep 21 '22 14:09

Bartosz