Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the Haskell rec keyword work?

Tags:

haskell

arrows

In arrow do notation, you can use the rec keyword to write recursive definitions. So for example:

rec     name <- function -< input     input <- otherFunction -< name 

How can this ever evaluate? It seems like it would just go into an infinite loop or something. I know it evaluates to the loop arrow combinator, but I don't understand how that works either.

EDIT: that powers example is really helpful. How would you write that with do notation, though? I assume you would need to use rec.

like image 250
Jeff Burka Avatar asked Mar 23 '11 13:03

Jeff Burka


People also ask

What does keyword do in Haskell?

To represent the do notation, we can simply use the 'do' keyword; after this, we can write our code that we want to execute. In most common general coding practice for Haskell, it usually comes up with the main module. This main module should always be followed by the do notation or any other logic, if any.

What does left arrow mean in Haskell?

The left arrow gets used in do notation as something similar to variable binding, in list comprehensions for the same (I'm assuming they are the same, as list comprehensions look like condensed do blocks), and in pattern guards, which have the form (p <- e). All of those constructs bind a new variable.


2 Answers

This bit of magic works due to haskells laziness. As you might know, Haskell evaluates a value when needed, not when defined. Thus, this works if you don't need the value fed in directly, or maybe later.

rec is implemented using the loop function of ArrowLoop. It is defined as followed:

class Arrow a => ArrowLoop a where         loop :: a (b,d) (c,d) -> a b c  instance ArrowLoop (->) where         loop f b = let (c,d) = f (b,d) in c 

You can see: The output is just fed back as the input. It will be calculated just once, because Haskell will only evaluate d when it's needed.

Here's an actual example of how to use the loop combinator directly. This function calculates all the powers of it's argument:

powers = loop $ \(x,l) -> (l,x:map(*x)l) 

(You could also write it like this instead: powers x = fix $ (x :) . map (*x))

How does it works? Well, the infinite list of powers is in the l argument. The evaluation looks like this:

powers = loop $ \(x,l) -> (l,x:map(*x)l) ==> powers b = let (c,d) = (\(x,l) -> (l,x:map(*x)l)) (b,d) in c ==> powers b = let (c,d) = (d,b:map(*b)d) in d ==> -- Now  we apply 2 as an argument powers 2 = let (c,d) = (d,2:map(*2)d) in d ==>          = let (c,(2:d)) = (d,2:map(*2)d) in c ==>          = let (c,(2:4:d)) = ((2:d),2:map(*2)(2:d)) in c ==>          = let (c,(2:4:8:d)) = ((2:4:d),2:map(*2)(2:4:d)) in  ==> -- and so on 
like image 123
fuz Avatar answered Oct 03 '22 23:10

fuz


Here is a real-ish example:

loop f b = let (c,d) = f (b,d) in c  f (b,d) = (drop (d-2) b, length b)  main = print (loop f "Hello World") 

This program outputs "ld". The function 'loop f' takes one input 'b' and creates one output 'c'. What 'f' is doing is studying 'b' to produce 'length b' which is getting returned to loop and bound to 'd'.

In 'loop' this 'd=length b' is fed into the 'f' where it is used in the calculation in drop.

This is useful for tricks like building an immutable doubly linked list (which may also be circular). It is also useful for traversing 'b' once to both produce some analytic 'd' (such as length or biggest element) and to build a new structure 'c' that depends on 'd'. The laziness avoids having to traverse 'b' twice.

like image 36
Chris Kuklewicz Avatar answered Oct 04 '22 01:10

Chris Kuklewicz