Skip to content

Instantly share code, notes, and snippets.

@hrldcpr
Created April 20, 2012 16:37
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hrldcpr/2430176 to your computer and use it in GitHub Desktop.
Save hrldcpr/2430176 to your computer and use it in GitHub Desktop.
Why Haskell is Cool
Haskell is cool!
Here are some reasons why.
(This is a Literate Haskell file, so you can load it and then follow
along with the examples by running `ghci whyhaskelliscool.lhs`)
"Pattern matching" syntax for defining functions is cool, letting you
avoid 'if' statements and simply write out the different behaviors of
a function:
> first (a, b) = a
> factorial 0 = 1
> factorial n = n * factorial (n - 1)
> len [] = 0
> len (first:rest) = 1 + len rest
In the last example we use the list constructor ':' (colon) which is
sometimes called 'cons' in other functional languages. It is a way to
construct a list but also an easy way to pattern-match on a nonempty
list and extract the first element and the rest of the list, perfect
for recursion.
(Aside: [1,2,3] is just syntactic sugar for 1:2:3:[])
Without pattern matching len could be written as:
> len' l = if null l
> then 0
> else 1 + len' (tail l)
Not as cool!
Inferred types are cool:
Solely based on our definition above,
you can ask ghci what the type of 'first' is (try it!)
ghci> :t first
first :: (t, t1) -> t
This means that first takes a tuple of any two types and returns a
value of the first type.
ghci> :t factorial
factorial :: Num a => a -> a
This means that for any numeric type 'a', factorial takes an argument
of that type and returns an argument of the same type.
ghci> :t len
len :: Num a => [t] -> a
This means that len takes a list of any type and returns something
numeric.
Recursive types are cool:
> data Tree a = Branch (Tree a) (Tree a)
> | Leaf a
> simpleTree = Branch (Leaf 1)
> (Branch (Leaf 2) (Leaf 3))
> leaves (Leaf x) = [x]
> leaves (Branch s t) = (leaves s) ++ (leaves t)
ghci> :t Leaf
Leaf :: a -> Tree a
ghci> :t Branch
Branch :: Tree a -> Tree a -> Tree a
ghci> :t leaves
leaves :: Tree t -> [t]
ghci> leaves simpleTree
[1,2,3]
Laziness is cool:
> zeros = 0 : zeros
> infiniteTree = Branch (Leaf "leaf") infiniteTree
> infiniteList x = x : (infiniteList x)
ghci> take 10 zeros
[0,0,0,0,0,0,0,0,0,0]
ghci> take 4 (leaves infiniteTree)
["leaf","leaf","leaf","leaf"]
ghci> take 5 (infiniteList "hi")
["hi","hi","hi","hi","hi"]
Currying (named after Haskell Curry) is really cool:
> increment = (+) 1
ghci> (+) 1 5
6
ghci> increment 5
6
Currying and type signatures go hand in hand.
For example:
ghci> :t map
map :: (a -> b) -> [a] -> [b]
We can read this as: 'map' takes a function and a list and returns a new list.
But thanks to currying it is equivalent to:
map :: (a -> b) -> ([a] -> [b])
which we can read as: map takes a function on elements and returns a function on lists.
Both interpretations are useful, and thanks to currying they are both true!
> incrementList = map increment
ghci> :t increment
Integer -> Integer
ghci> :t incrementList
[Integer] -> [Integer]
ghci> incrementList [1, 2, 3]
[2,3,4]
Monads are cool, and are mostly simpler than they sound.
In particular:
1. The List monad is equivalent to "list comprehensions" in most languages.
> mapped f l = do
> x <- l
> return (f x)
ghci> mapped (* 2) [1, 2, 3]
[2,4,6]
> cartesianProduct a b = do
> x <- a
> y <- b
> return (x, y)
ghci> cartesianProduct [1, 2, 3] ["a", "b"]
[(1,"a"),(1,"b"),(2,"a"),(2,"b"),(3,"a"),(3,"b")]
2. The Maybe monad is like a cool combination of "null values" and
"exceptions" in most languages.
The two Maybe values are 'Just x', for any specific x, and 'Nothing'.
The builtin function 'lookup' takes an association list and a key and
returns Just the value of that key if it is present, or Nothing otherwise.
ghci> lookup "a" [("a", 1), ("b", 2)]
Just 1
ghci> lookup "c" [("a", 1), ("b", 2)]
Nothing
So far this is a lot like null values in other languages, but when you
start doing things within the Maybe monad it becomes more like exceptions
in other languages, because a single Nothing value short-circuits to the
end of the computation.
A contrived example:
> year = 2011
> birthyears = [("Harold", 1985), ("Jackson", 1988)]
> age name = do
> birthyear <- lookup name birthyears
> return (year - birthyear)
ghci> age "Jackson"
Just 23
ghci> age "Antony"
Nothing
This is cool!
In most languages, you would have to check that 'birthyear' is valid
before you try to subtract it from 'year', or you'd get some sort of
null error or type error.
But since we're in the Maybe monad, the moment lookup returned Nothing
the entire computation returned Nothing!
Thus there is no such thing as a null value in Haskell, or a null
pointer exception; the type system simply doesn't allow it.
3. Finally, the IO monad is a way to contain operations with side
effects, which you must explicitly execute, allowing everything else
to remain "purely functional" / free of side effects.
> printAge = do
> name <- getLine
> print (age name)
ghci> :t getLine
getLine :: IO String
ghci> :t printAge
printAge :: IO ()
4. Monads are a cool generalization!
All we used in our definition of 'mapped' earlier was the standard
monad operations 'do', '<-', and 'return', so maybe they should work
for any monad? Let's see:
ghci> :t mapped
mapped :: Monad m => (t -> b) -> m t -> m b
Cool, Haskell has already inferred that our definition was very general!
So for any monad type, mapped takes a function and a monad element and
returns a new monad element.
ghci> mapped (/ 10) (Just 5)
Just 0.5
ghci> mapped (/ 10) Nothing
Nothing
Cool.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment