Created
April 20, 2012 16:37
-
-
Save hrldcpr/2430176 to your computer and use it in GitHub Desktop.
Why Haskell is Cool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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