The article I wrote yesterday was just the beginning, today we’ll look at the next step in becoming Haskell experts.
Yesterday we’ve learned how to split up our program and how to compile, or run it. Today we’ll look at some basic features of Haskell.
Haskell is strongly typed, meaning that the compiler knows the type of every object we have in our program. Yet we won’t see types too often in Haskell because it uses type inference, which means that the compiler deduces the type of your objects. We can help it by providing type information, or include it to be sure that our objects have the specified type.
Functions are often very generic and require not a specific type, but require that the type is the same at certain places. Let’s look at the type of the
map function (we query the type via GHCi, the interactive shell of GHC, just start it with
Prelude> :t map map :: (a -> b) -> [a] -> [b]
So, map has the type: “first parameter is a function which takes type ‘a’ and returns type ‘b’, the second parameter is a list of objects with type ‘a’ and the returned object is of type list of objects of type ‘b’”. So, map takes function and a list, and returns another list. ‘a’ and ‘b’ aren’t further specified, and
map really doesn’t care what is is.
Look at this example:
map (\x -> x 40) [(+1), (*2), (/4)]
here the objects in the list are (partially applied) functions, and the function to map on the list is a lambda (an anonymous function), which applies the given argument to 40.
If we run the above code (again in ghci), we get:
Prelude> map (\x -> x 40) [(+1), (*2), (/4)] [41.0,80.0,10.0]
Please note, that a list is always composed of objects of the same type, so in this case it’s the most general one. We can’t mix integers and characters in a list, but we can use tuples:
The previous example used a lambda function, this is nothing special, just a function without a name. We use it if we define some small function which isn’t used more than once.
Let’s define a lambda which takes two integers and returns the product of the two:
\x y -> x*y
We could also give it a name:
product = \x y -> x*y
but then we would write it like
product x y = x*y
Partially applied functions
This is where I felt the urge to learn Haskell: we can partially apply a function! We’ve already seen it above (for example
Let’s define a function to increment an integer,
inc1 a = a + 1
we could also write the above function as
inc2 = (+1)
The difference is the type,
inc1 returns an integer, whereas
inc2 returns a function! Of course both act the same, but there is a difference.
Lets look at another example:
map (+) [1,2,4,5]
this returns a list of partially applied functions! It returns
[(+1), (+2), (+4), (+5)].
One last example,
filter can be used to, well, filter some list. Lets filter out all number larger than 4:
filter (<=4) [1,2,3,4,5,6]
Again – we used a partially applied function! We can do quite a lot of amazing things with such easy and powerful abstractions, and that’s the reason why Haskell is known for it’s glue capabilities.
The last concept I’d like to show today is the combination operator: (.).
Using this operator, we create a new function which takes the arguments of the first, and returns the return-value of the second.
(f.g)(x) is the same as
f(g(x)). So, why is this important? Well, as I’ve said in my previous post, Haskell is full of syntactic sugar, so using the combination operator we save some keystrokes.
Here a simple example which extends the previous filter example to include only those numbers whose double is smaller than 5 (smaller or equal to 4).
filter ((<=4).(*2)) [1,2,3,4,5,6]
Another way would be to use a simple lambda function:
filter (\x -> (2*x) <= 4) [1,2,3,4,5,6]
The combination operator makes code immensely readable, no useless arguments and parentheses which clutter up our programs, just plain functionality.
Look at this imitation of the
rev command in Unix (taken from here):
main = interact (unlines . map reverse . lines)
could it get any simpler?
You know, take Lisp. You know, it’s the most beautiful language in the world. At least up until Haskell came along.
— Larry Wall (Creator of Perl)