1

FYI: Second day into Haskell

MRE (what the function does, does not matter):

foo :: Int -> Int -> Int
foo x y
        | x == y = x + y
        | x < y = x
        | x > y = y

This is what I've gathered about the -> operator(?) so far:

  1. It is right-associative
  2. It is used in function type declaration to denote the type of function parameters as well as its return type

Now take a look at the following function type declaration:
foo :: Int -> Int
This declares(what is the appropriate word?) a function called foo that takes an Int and returns an Int.

However, the declaration: foo :: Int -> Int -> Int declares(?) that foo takes 2 Ints and returns an Int when it is the same as saying foo :: Int -> (Int -> Int) which is a function that takes an Int and returns a function that takes an Int and returns an Int.

How does this make sense? Should it not be something on the lines of Int,Int -> Int?

PS: The book I'm using: Thinking Functionally with Haskell by Richard Bird.

3
  • 1
    Your intuition is right, foo:: Int -> Int -> Int is "a function that takes an Int and returns a function that takes an Int and returns an Int", but when function application is written as foo a b the differentiation is almost meaningless, it's the same as (foo a) b
    – cafce25
    Commented Jun 24 at 19:49
  • No function takes more than one parameter. Commented Jun 24 at 19:52
  • 1
    foo is a function that produces another function, foo a is that function applied to a, since foos return value is a function we can appy that to another value: foo a b is the returned function foo a applied to b.
    – cafce25
    Commented Jun 24 at 19:57

1 Answer 1

4

No function takes two parameters. All functions take one parameter. Indeed, we can write:

bar :: Int -> Int
bar = foo 2

So what did foo 2 do? It constructed a new function, a function that will take an Int and return an Int. It is essentially a "specialized" version of foo with x = 2.

So foo :: Int -> Int -> Int, is like your intuition said Int -> (Int -> Int): it takes one parameter, an Int, and constructs a function that will then take the "second" parameter.

You can also make an "uncurried" version of foo:

foo' :: (Int, Int) -> Int
foo' (x, y)
  | x == y = x + y
  | x < y = x
  | x > y = y

Now it again takes one parameter: a 2-tuple with two Ints, but thus one parameter.

Converting between the two styles happens regularly if you want to "group" parameters together, or ungroup them. Therefore there are uncurry :: (a -> b -> c) -> (a, b) -> c [Hackage] and curry :: ((a, b) -> c) -> a -> b -> c [Hackage], so foo' = uncurry foo, and foo = curry foo'.

6
  • um, if I take a list instead of a tuple (I still don't know how to specify a list which has only 2 members), will that still be a curried function?
    – kesarling
    Commented Jun 24 at 20:03
  • similarly, I take it that curry :: ((a, b) -> c) -> a -> b -> c and curry :: (a, b -> c) -> a -> b -> c are different as the latter is the same as curry :: (a, (b -> c)) -> a -> b -> c?
    – kesarling
    Commented Jun 24 at 20:05
  • @kesarling: no, a list is also not very useful, since it requires that the elements all have the same type, and a list also has no fixed size. A (2-)tuple on the other hand has a fixed size, and the two items can have a different type. Commented Jun 24 at 20:06
  • yes, you are right that curry :: (a, b -> c) -> a -> b -> c and curry :: (a, (b -> c)) -> a -> b -> c are the same, but not the same as the real curry function. Commented Jun 24 at 20:07
  • 1
    @kesarling: no, it is just part of the identifier, just like in math to denote that two things are (very) similar stackoverflow.com/a/5673954/67579 Commented Jun 24 at 20:12

Not the answer you're looking for? Browse other questions tagged or ask your own question.