Haskell is a functional language, so function calls and function definitions form a major part of any Haskell program. That's why the syntax for those two constructs is reduced to a bare minimum. Admittedly, this makes Haskell programs hard to read for newcomers. This tutorial deals with the function call syntax and the next one will cover function definition syntax. Normally, I don't like starting a language course with syntax, but in my experience the "sink or swim" approach of learning basic syntax on the go while solving programming exercises doesn't work very well with Haskell.
Function Application
What does it mean "bare minimum"? It means that any series of identifiers is a function call or, as we often call it, a function application. This:
a b c d
is an application of a function a
to three arguments b
, c
, and d
. That's it! There is no other interpretation of it. If a
is not a function, this is not valid Haskell.
Try it! First run this code (click the Run
button):
a b c d =
"Function a called with arguments " ++ b ++ " " ++ c ++ " " ++ d
b = "b"
c = "c"
d = "d"
main = putStrLn $
-- show (program fragment, to see the rest, press the "Show All" button)
a b c d
-- /show
Then try to change the order of identifiers, so that b
is the first identifier (just edit the code in place and run it again). The compiler will try to interpret b
as a function and it will fail because I made b
a string (which is the synonym for [Char]
, as we'll see later).
You may parenthesize function application if you need to. Those two are equivalent:
f a b
and
(f a b)
But you should definitely unlearn the traditional interpretation of this syntax:
f (a, b)
This is still valid Haskell, provided f
is a function that takes a pair, (a, b)
(also called a binary tuple) as an argument.
You'll see the full impact of the simplified function syntax when we talk about currying.
Precedence
Another important thing to know is that function application has the strongest binding power. I'll demonstrate it in a simple example, but first let's talk a little about arithmetics. No weirdness there: Haskell defines a bunch of infix operators like +
, -
, *
, /
, etc. It also has numbers, both integral and floating-point.
So how would you interpret this?
a b * c d
As I said, function application has the highest precedence, so this code is equivalent to:
(a b) * (c d)
It is not equivalent to:
a (b * c) d
The former says: apply function a
to b
and multiply the result by function c
applied to d
. The latter is function a
applied to two arguments, b * c
and d
. (In production code, identifiers usually have more meaningful names. Here, I want to show you pure syntax.)
Can you predict the result of the following code? Here sq
is a function that squares its argument.
sq b = b * b
main = print $
-- show
sq 3+1
-- /show
(If you're surprised by the result, read about precedence again.)
There is one case where precedence rules may be confusing: the unary minus. If you want to apply a function f
to negative one, you have to use parentheses, as in:
f (-1)
If instead you write:
f -1
the compiler will try to subtract one from f
-- not a good idea if f
is a function. But if you make such a mistake, the compiler will easily catch it. This will become obvious when we talk about types.
One more thing: If you knew that f
and g
were functions, how would you interpret this?
f g x
According to the rules I just described, f
must be a function and g
and x
its arguments. Yes, a function may take another function as an argument. If you wanted g
to act on x
instead, you'd use parentheses:
f (g x)
Dollar and Dot Notation
When we talked about precedence I said that function application has higher precedence than all other operators. But function application is not really an operator. Or is it? If you stretch your imagination, you may think of the whitespace between the function name and the argument as an operator. This is not strictly true, because in some cases you may elide the space if the parse is unique (try removing all spaces from the solution to Exercise 1 at the bottom of this tutorial). When you learn about currying, you'll see that spaces between arguments play exactly the same role as the space between the function and the argument.
This "operator whitespace" has the highest precedence and binds to the left. Because of left binding, the application of multiple functions requires multiple parentheses. For instance, if you want to square the square root of a number, you write:
sq x = x * x
main = print $
-- show
sq (sqrt (7 + 9))
-- /show
There is, however, another operator (this time a real operator) for function application, denoted by the dollar sign $
. Even though it serves the same purpose, in a way it's the opposite of the "operator whitespace." It has the lowest precedence and binds to the right. Because of that, the example above can be rewritten without parentheses:
sq x = x * x
main = print $
-- show
sq $ sqrt $ 7 + 9
-- /show
Because $
binds to the right, the square root will be executed first; and because it has the lowest precedence, the addition will be performed before function application.
Applying a function to the result of another function is called function composition and has its own operator, the dot, .
. This operator has very high precedence, surpassed only by by that of function application.
The composition of sq
with sqrt
may be written as sq . sqrt
. This new function, when acting on a number will first take its square root and then square the result, so this will work too:
sq x = x * x
main = print $
-- show
(sq . sqrt) $ 7 + 9
-- /show
This particular composition is not very interesting, since it doesn't do anything to a number except of possibly losing precision. The function that really does nothing is called the identity, id
. Composing identity with any function doesn't change the behavior of that function. Try it:
sq x = x * x
main = print $
-- show
(sqrt . id) 256
-- /show
Conclusion
Function syntax in Haskell might seem weird at first. But consider that Haskell programs are built from functions. In particular, function application is one of the most common operations. Its syntax should be the tersest; just as the most common letter in English, 'e', is encoded as a single 'dot' in the Morse alphabet.
In the next tutorial we'll learn how to define a function in Haskell.
Exercises
I defined a function
pyth
that takes two numbers and returns the sum of their squares. Add parentheses to the code below to make it compile (don't get scared by unintellegible error messages):pyth a b = a * a + b * b main = print $ -- show pyth 3 * 2 pyth -1 8 -- /show
pyth a b = a * a + b * b main = print $ -- show pyth (3 * 2) (pyth (-1) 8) -- /show
I also defined a function
pyth'
(yes, you can use apostrophes -- or 'primes', as they are called in mathematics -- in identifiers).pyth'
takes a tuple of numbers, as in(a, b)
, and returns the sum of their squares. Add parentheses and commas to code below to make it compile. You may reduce the number of parentheses if you take into account that the comma inside a tuple has lower precedence than arithmetic operators.pyth' (a, b) = a * a + b * b main = print $ -- show pyth' 3 * 2 pyth' -1 8 -- /show
pyth' (a, b) = a * a + b * b main = print $ -- show pyth' (3 * 2, pyth' (-1, 8)) -- /show
The
print
function prints its argument, as long as it is convertible to a string. Numbers are convertible to strings. The code below works but looks more like Lisp than Haskell. Try to remove as many parentheses as you can using$
signs (Hint: With some cleverness, you can get rid of them all).pyth a b = a * a + b * b main = do -- show print (sqrt (pyth 3 ((-1) - 3))) -- /show
pyth a b = a * a + b * b main = -- show print $ sqrt $ pyth 3 $ -1 -3 -- /show