Some notes in Haskell
Lazy Evaluation
Why does a ++ (b ++ c)
faster than a ++ (b ++ c)
?
++
traverses the left parameter and append each item of it to the right parameter which leads to a worse performance when using the wrong association.
In short, the bad association gives a quadratic amount of traversal as it redoes all the traversals it has already done, plus one more, at each invocation of (++)
, while the good association traverses each list at most once.
Functor
What's the definition of Functor?
Functor is a type class, the definition of Functor is:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Each Functor f is a type (type constructor) with exactly one type slot.
The operator that Functor f support is fmap
.
Instance Functor Maybe
or Instance Functor (Maybe m)
?
Functor is a special type class, for we define Functor type class as:
class Functor f where
fmap :: (a -> b) -> f a -> f b
In classical type classes, which is defined such as following:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
there a denotes a type variable which can be think as Int
, Double
, Char
and so on. When we implement this type class, we use instance
clause such as following:
instance Eq TrafficLight where
Red == Red = True
Green == Green = True
Yellow == Yellow = True
_ == _ = False
where TrafficLight
is a concrete type,
Think of that a functor is also a type class, when we implement functor type class, we use following statements:
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
Question 1: When we implement the Eq
type class, we use a concrete type TrafficLight
; but when we implement the Functor
type class, why we can use Maybe
which is just a type constructor and not a type?
Answer: Just as an answer in Stack Overflow question What is a functor in functional programming, we can just think Maybe
as a more general type with some collective properties, it can take a type such as Int
as its parameters and generate a new type Maybe Int
, which is a concrete type not so general as type Maybe
is.
So the right answer is Instance Functor Maybe
.
How to understand Function as Functor
and Function as Applicative
?
First, how to understand function as functor?
We can regard functor as an empty box such as:
instance Functor Maybe where
fmap :: (a -> b) -> f a -> f b
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
there, Maybe
type can be seen as an empty box with one slot which take a type to generate a concrete type Maybe a
. In the fmap
function:
- The first parameter is a function, which maps from a to b;
- The second parameter is a value of the type with the slot filled(concrete type), this concrete type is generated by type constructor and has the type f a (f is
Maybe
, so f a isMaybe a
).
When we implement function functors, for function functors must have two parameters to make a type a -> b
, if we want our function functor has exactly one slot, we should first fill a slot, so the type constructor of function functor is ((->) r):
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
As the same as the fmap
function in Maybe
Functor, we should regard the second parameter g as a value of a concrete type which is generate by f (f equals (->) r
), so f a is (->) r a
which can be seen as r -> a
. Finally, it is not difficult to understand that the g x in the fmap
function cannot be seen as r -> x
, it is just a function application which can be seen as (r -> a) x
, also (x -> a)
.
Finally, it is not hard to understand that the <*> function in Applicative function (->) r
can be implemented as following:
<*> :: f (a -> b) -> f a -> f b
<*> :: (r -> a -> b) -> (r -> a) -> (r -> b)
<&> :: (a -> b) -> (r -> a) -> (r -> b)
f <*> g = \r -> f r (g r)
for g r will map r to a, f r a will map r, a to b, so the whole lambda function can be seen as r -> b
, also f b
. For an instance:
((+) <*> (+3)) 5
the result is 5 + (5 + 3) = 13.
Applicative Functor
What's the definition of Applicative Functor?
Applicative Functor is a type class, it is also a Functor, the definition of Applicative Functor is:
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
Each Applicative Functor f is a type (type constructor) with exactly one type slot.
The operator that Applicative Functor f support is pure
, <*>
and <$> (Just another name for fmap)
.
How to understand in functions as applicatives using applicative style: (+) <$> (+3) <*> (*100) $ 5
= 508?
We know (+)
has type: Num a, a -> a -> a
;
We also know (+3)
and (*100)
has type: Num r, a, r -> a
;
(+) <$> (+3)
equals pure (+) <*> (+3)
, where :t pure (+)
equals Num _, a, _ -> a -> a -> a
In another words, the pure (+)
simply takes a _
parameter whatever and return the +
operator, the parameter _
has no effect on the final return value. pure (+)
also maps the return value of function (+3)
to a function. Now for
f <*> g = \r -> f r (g r)
we can apply the operators and get:
pure (+) <*> (+3) =
\r -> f r (gr) =
\r -> + (gr) =
\r -> + (r + 3) =
\r x -> x + (r + 3)
it has the type r -> x -> a
. We then calculate pure (+) <*> (+3) <*> (*100)
using the definition of <*>, and get:
pure (+) <*> (+3) <*> (*100) =
\r -> f r (gr) =
\r -> (r + 3) + (gr)
\r -> (r + 3) + (r * 100)
then we apply this function with parameter 5, we get:
(5 + 3) + (5 * 100) = 508
we can simply think this applicative style as first to calculate the value after <$>
and sum them up with the operator before <$>
. In last example, this operator is a binary operator equals (+)
, we can replace it with a triple operator (\x y z -> [x,y,z])
, so the following equation holds:
(\x y z -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5 = [8.0,10.0,2.5]
Monoid
What's the definition of Monoid?
Monoid is a type class, this type class is for types whose values can be combined together with a binary operation. The definition of Monoid is:
class Monoid m where
mempty :: m
mappend :: m -> m -> m
mconcat :: [m] -> m
First, we see that only concrete types can be made instances of Monoid, because the m in the type class definition doesn’t take any type parameters. This is different from Functor and Applicative, which require their instances to be type constructors that take one parameter.
The operator that Monoid m support is mempty
, mappend
and mconcat
.
Monad
What's the definition of Monad?
Monad is a type class, it is also an Applicative Functor, the definition of Monad is:
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
fail :: String -> m a
fail msg = error msg
Each Monad m is a type (type constructor) with exactly one type slot.
The operator that Monad m support is return (Just another pure)
, >>= (beefed-up <*>)
, >>
and fail
.
How to understand the implementation of State s
Monad
The implementation of Monad State s
is:
instance Monad (State s) where
return x = State $ \s -> (x, s)
(State h) >>= f = State $ \s -> let (a, newState) = h s
(State g) = f a
in g newState
Well, the result of feeding a stateful computation to a function with >>=
must be a stateful computation, right? So, we start of with the State
newtype wrapper, and then we type out a lambda. This lambda will be our new stateful computation. function h has type s -> (a, s)
, and the result function has type s -> (b, s)
. The whole implementation of >>=
do is passing a state to the function h, computing its new state and pass the new state to function g implied in f, getting the newer state. So that's not strange that when we define following function:
import Control.Monad.State
stackManip :: State Stack Int
stackManip = do
push 3
a <- pop
pop
and we execute it with runState stackManip [5,8,2,1]
. The first state is [5,8,2,1]
, we pass the state to the push 3
and a newer state [3,5,8,2,1]
is generated when it turns to a <- pop
, and so on.