Search code examples
haskelltypeslambdafunctional-programmingunfold

How lambda function in unfoldr can be used with two arguments in Haskell?


When reading this article I found example of generating Fibonacci sequence with unfoldr function:

fibs = unfoldr (\(a,b) -> Just (a,(b,a+b))) (0,1)

But when I looked at documentation I found that lambda in unfoldr function takes only one argument b:

unfoldr :: (b -> Maybe (a, b)) -> b -> [a]

Examples in the doc also demonstrate only one argument usage:

unfoldr (\b -> if b == 0 then Nothing else Just (b, b-1)) 10

So, I'm interested how can it be applied to two arguments as in Fibonacci example?


Solution

  • This is your function:

    GHCi> let foo = \(a,b) -> Just(a,(b,a+b))
    

    While we can certainly interpret it in our heads as a function of two arguments, as far as Haskell is concerned it only takes one argument...

    foo :: Num t => (t, t) -> Maybe (t, (t, t))
    

    ... a pair of type (t, t) for some t in the Num class. All that is going on in your example, then, is that the b in unfoldr's signature is being replaced by Num t => (t, t), and a by Num t => t, resulting in:

    Num t => ((t, t) -> Maybe (t, (t, t))) -> (t, t) -> [t]
    

    Some extra evidence of that:

    GHCi> :t unfoldr (\(a,b) -> Just (a,(b,a+b)))
    unfoldr (\(a,b) -> Just (a,(b,a+b))) :: Num a => (a, a) -> [a]
    

    With your immediate question answered, here is a little digression. Another way of passing two arguments to a Haskell function is passing them separately, as opposed to in a pair:

    GHCi> let foo2 a b = Just(a,(b,a+b))
    GHCi> :t foo2
    foo2 :: Num t => t -> t -> Maybe (t, (t, t))
    

    Obviously, if you still have foo around you don't even need to rewrite the implementation:

    GHCi> let foo2 a b = foo (a, b)
    GHCi> :t foo2
    foo2 :: Num t => t -> t -> Maybe (t, (t, t))
    

    In fact, this transformation -- which in jargon is called currying -- is always possible, and there is even a function for doing that...

    GHCi> let foo2 = curry foo
    GHCi> :t foo2
    foo2 :: Num t => t -> t -> Maybe (t, (t, t))
    

    ... as well as another one for going in the opposite direction:

    GHCi> :t uncurry foo2
    uncurry foo2 :: Num t => (t, t) -> Maybe (t, (t, t))
    

    What might be surprising in all that, however, is how even foo2, which looks even more like a function of two arguments than foo, still is a function of one argument! The trick is that a signature like that of foo2...

    foo2 :: Num t => t -> t -> Maybe (t, (t, t))
    

    ... has some omitted, optional parentheses. The omission conceals the fact that the function arrow, ->, is right-associative. If we add them, we get:

    foo2 :: Num t => t -> (t -> Maybe (t, (t, t)))
    

    That is, foo2 takes one argument (that is, our first argument) and returns another function which also takes one argument (that is, our second argument).


    The main takeaway, then, is that all Haskell functions take just one argument. Fortunately, it is very easy to write functions that work as if they took two arguments. More often than not, that is done by writing the function in a curried way (that is, as a function like foo2 that returns another function), but sometimes it is necessary or convenient to pass multiple values in a pair, as you had to do in your example.