Haskell is a lazy, functional programming language created in the late 1980’s by a committee of academics.
- functional: functions are first-class, centered around evaluating expressions rather than executing instructions.
- pure: immutable, no side effects, calling the same function with the same arguments results in the same output every time.
- lazy: call-by-need, expressions are not evaluated until their results are actually needed.Alone with Memoization
- statically typed: as Swift.Every Haskell expression has a type, and types are all checked at compile-time.
- abstraction: parametric polymorphism, higher-order functions
- wholemeal programming:
“Functional languages excel at wholemeal programming, a term coined by Geraint Jones. Wholemeal programming means to think big: work with an entire list, rather than a sequence of elements; develop a solution space, rather than an individual solution; imagine a graph, rather than a single path. The wholemeal approach often offers new insights or provides new perspectives on a given problem. It is nicely complemented by the idea of projective programming: first solve a more general problem, then extract the interesting bits and pieces by transforming the general program into more specialised ones.” — Ralf Hinze
1
2
3
4
5 > int acc = 0;
for ( int i = 0; i < lst.length; i++ ) {
acc = acc + 3 * lst[i];
}
>
to
1
2 > sum (map (3*) lst)
>
Lazy evaluation
Strict evaluation
1 | f (release_monkeys(), increment_counter()) |
If the releasing of monkeys and incrementing of the counter could independently happen, or not, in either order, depending on whether f happens to use their results, it would be extremely confusing. When such “side effects” are allowed, strict evaluation is really what you want.
Side effects and purity
By “side effect” we mean anything that causes evaluation of an expression to interact with something outside itself.The root issue is that such outside interactions are time-sensitive.
- No global variable
- No output to screen
- No reading from a file or the network
WTF……
The solution is IO monad.
Consequences
- Purity
- Tricky space usage
1 | -- Standard library function foldl, provided for reference |
Consider if there is a very long list, it leads to stack overflow.
- Short-circuiting operators
1 | (&&) :: Bool -> Bool -> Bool |
- Infinite data structures
- Pipelining/wholemeal programming
- due to laziness, each stage of the pipeline can operate in lockstep, only generating each bit of the result as it is demanded by the next stage in the pipeline.
- Thunk.
- Confilct with parallel computing.
Functors
- map :: (a -> b) -> [a] -> [b]
- treeMap :: (a -> b) -> Tree a -> Tree b
- maybeMap :: (a -> b) -> Maybe a -> Maybe b
why not
thingMap :: (a -> b) -> f a -> f b
Since f is type variable, we can make a type class, which is traditionally called Functor:
1 | class Functor f where |
Applicative functors
1 | type Name = String |
So Employee is
Employee :: Name -> String -> Employee
However, sometimes we want this:
(Name -> String -> Employee) -> Maybe Name -> Maybe String -> Maybe Employee
and why not provide this as well:
Name -> String -> Employee) -> [Name] -> [String] -> [Employee]
Seems wec can use fmap
to do this job,
1 | class Functor f where |
The reason why we cannot implement fmap2 is fmap h fa :: f (b -> c)
, and we cannot handle f (b -> c)
with f b
So it’s time to introduce Applicative
1 | class Functor f => Applicative f where |
1 | liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c |
Law for
Applicative
:
1
2 > f `fmap` x === pure f <*> x
>
Monads
From functor and applicative, we can see computations with a fixed structure.
However, sometimes we want to be able to decide what to do based on some intermediate results.
1 | newtype Parser a = Parser { runParser :: String -> Maybe (a, String) } |
Suppose we are trying to parse a file containing a sequence of numbers, like this:
4 78 19 3 44 3 1 7 5 2 3 2
So the example above could be broken up into groups like this:
78 19 3 44 – first group
1 7 5 – second group
3 2 – third group
So what we need is some thing like this:parseFile :: Parser [[Int]]
Applicative gives us no way to decide what to do next based on previous results: we must decide in advance what parsing operations we are going to run, before we see the results.
1 | class Monad m where |
m
refer to monads and m a
refer to mobits. A mobit of type m
a represents a computation which results in a value (or several values, or no values) of type a
.
A function which will choose the next computation to run based on the result(s) of the first computation.
So all (>>=) really does is put together two mobits to produce a larger one, which first runs one and then the other, returning the result of the second one.