Haskell's typeclasses
Haskell's Functor is a type class. Any type constructor that implements Functor's fmap is a functor. To be more technical, the type constructor + implementation of fmap is an endofunctor on Hask.
If ((,) e)
is a type constructor, it means a tuple type of two elements with the first being e
while the second is a type variable.
Note that ((,) 1)
is a function. It's type is not Num a => ((,) a)
For Monad, think of m a
as a computation with a
being the return result. return
means "null computation" which accepts a
and returns a
in the computation context. >>=
composes two computation into another computation.
Based on this, it's easy to understand the reader Monad.
const a b = a
instance Monad ((->) e) where
return = const
m >>= f = \x -> f (m x) x
Here, const
will return a computation which simply ignores the arguments passed in and return value passed to return
. m
is a computation. >>=
combines one computation with another computation generator, that is f
. f
generates another computation based on the result of computation m
.
What can be a reader monad? Every function that accepts an argument and returns a value can be a reader monad. For example, sometimes the argument passed in can be an environment. The function will lookup some variable in the environment and return it directly or modify it in the environment.
How to prove two laws are equivalent?
No Idea
We already have fmap, why do we still need liftM?
Theoretically, every Monad is also a Functor. So 'fmap == liftM'. However, in haskell's standard library, Monad type class doesn't require Functor instance. So we have 'liftM' to avoid an explicit 'Functor' constraint.
Multi-parameter type class and Functional Dependencies.
Take type class Eq
as an example, Haskell's type class declaration is like:
class Eq a where
(==) :: a -> a -> Bool
The declaration can be read as:
Type a is a member of type class
Eq
So Haskell's type class is like a set of types with the types (also called instance) follow the constraint of the class by implementing all the function declared.
instance Eq Bool where
a == b = if a then b else not b
Here, we declare that Bool
belongs to set Eq
, or $ Bool \in Eq $. The curry style declaration is a little misleading.
We can also declare one type class as the super class of another.
class Eq a => Ord a where
(<), (<=) :: a -> a -> Bool
This declaration can be read as
Type class
Ord
implies Type classEq
Or, if a type is a member ofOrd
, then it must be a member ofEq
Or,Eq
is a super class ofOrd
Super class declaration makes sure that one type class can inherit the constraint of another type class.
Haskell also allows multi-parameter type class. One classical example is to implement uniform interfaces for Collection.
class Collection e ce where
empty :: ce
insert :: e -> ce -> ce
member :: e -> ce -> Bool
List
is an instance of Collection
type class.
instance Collection Int [Int] where
empty = []
insert = (:)
member = elem
Run a small program to check out type class
demo = let x :: Int
x = 3
in insert x empty
main = print demo
Compile it with GHC, it will report some scary errors:
multi-param.hs:67:12:
No instance for (Collection Int a0) arising from a use of ‘insert’
The type variable ‘a0’ is ambiguous
Relevant bindings include demo :: a0 (bound at multi-param.hs:65:1)
Note: there is a potential instance available:
instance Collection Int [Int] -- Defined at multi-param.hs:59:10
In the expression: insert x empty
In the expression:
let
x :: Int
x = 3
in insert x empty
In an equation for ‘demo’:
demo
= let
x :: Int
x = 3
in insert x empty
...
The reason is that when GHC evaluating insert x empty
, it will firstly infer the types of expressions involved and then find the instance by unifying the types of expressions.
x :: Int
empty :: Collection e ce => ce
insert :: Collection e ce => e -> ce -> ce
Apparently, x
's type is known because we have explicitly annotated it. However, type of empty
is unknown, so GHC directly represents it as a type variable $ \alpha $. Based on x
and empty
, we will get type of insert
: $ Collection \space Int \space \alpha => Int \rightarrow \alpha \rightarrow \alpha $. We haven't declared the instance $ Collection \space Int \space \alpha $, which leads GHC to complain.
Faced with the problem, you might be tempted to annotate type of empty
explicitly.
demo = let x :: Int
x = 3
ec :: [Int]
ec = empty
in insert x ec
This still doesn't work since the empty
function has an ambiguous type: empty : Collection e [Int] => [Int]
since type variable e
appears on the left of =>
symbol(See qualified types). (I don't quite get this, maybe I will read the paper first). Also the instance Collection e [Int] => [Int]
is also not defined, which is just what GHC reports.
We can solve this by replacing empty
with []
, but this makes empty
function meaningless. Functional dependency is one reasonable solution.
class Collection e ce | ce -> e where
empty :: ce
insert :: e -> ce -> ce
member :: e -> ce -> Bool
instance Collection Int [Int] where
empty = []
insert = (:)
member = elem
demo = let x :: Int
x = 3
ec :: [Int]
ec = empty
in insert x empty
Here, ce -> e
is functional dependency. It means that ce determines e
. Or, if you have already determined ce
, then e
is determined. Or, for if ce
is determined, there is only one corresponding instance implementation.
The annotation is quite intuitive. ce -> e
looks like a function, whose definition requires for each ce
there is only one corresponding e
.
Unfortunately, we still can't write code like:
demo = let x :: Int
x = 3
in insert x empty
Since without explicit annotation, the type of empty
is still unknown, which leads to wrong inferred instance: Collection Int a
.)
OCaml's Modules are claimed to be much more expressive than type class.
ReaderT
ask :: (Monad m) => ReaderT r m r
ask = ReaderT return
return
is a polymorphic function with type: a -> m a
, which just fits in ReaderT
's constructor. The type of m
depends on how you defines it. A small demo to show how ask
works:
demo = let y :: Int
y = 3
x :: Either String Int
x = runReaderT (ReaderT return) y
in x
Here, I define m
as Either Monad. You can also define it as Maybe Monad or whatever you want.
Continuation Monad
take function square as an example
square x = x*x
-- change it into continuation passing style
square_cont x = \k -> k (square x)
The return value of square_cont
is called suspended computation. After passing a continuation:k
to suspended computation, it will calculate the intermediate result(returned by square
function). The return value of k
is the return value of the suspended computation.
Suspended computation "wraps" all the computation of "square" and pass the result to "future computation"/Continuation of square
:k
. The main responsibility of suspended computation is getting k
, conducting the wrapped computation and passing computation result to k
.
Get a sense of suspended computation through following examples:
main = let suspended_computation = square_cont 3
in do
print $ suspended_computation (*2)
print $ suspended_computation (*3)
Continuation Monad is just the suspended computation. Continuation monad can be used to compose functions in continuation passing style.
newtype Cont a r = Cont {runCont :: (a->r) -> r}
for example:
import Control.Monad.Identity
import Control.Monad.Cont
square:: (Num a) => a -> a
square x = x*x
square_cont :: (Num a) => a -> Cont b a
square_cont x = cont $ \k -> k (square x)
main = print $ runCont (square_cont 4) show
cont
construct a continuation monad. runCont
execute the continuation monad after passing continuation to it.
For Cont b a
, b
is final result type(return type of future continuation), a
is intermediate result type.
A continuation monad transformer can bind a future continuation with a monadic computation to form a continuation monad. The future continuation accepts the result of monadic computation and return a monadic computation with new result.
callCC
callCC or call-with-current-continuation, gives explicit control of continuation.
foo = callCC $ \k -> (k "hello world") >> (return "never printed")
main = print $ runCont foo show
callCC returns a suspended computation, which is often called "callCC computation". For whatever value passed to k is captured in the callCC computation, and the computation after k is ignored.
-- callCC accepts a function f and return a Continuation Monad.
-- f accepts a function k as arguments
-- f :: (a -> ContT r m b) -> ContT r m a
-- k :: a -> ContT r m b
-- x :: a
-- c :: a -> m r
callCC::((a -> ContT r m b) -> ContT r m a) -> ContT r m a
callCC f = ContT $ \ c -> runContT (f (\ x -> ContT $ \ _ -> c x)) c
In callCC
, c
is actually "current continuation", i.e, the continuation of the callCC
expression, or, what the program will do after the callCC
is called. So, you can see, it's called "call with current continuation" not without reason.
All the magic of callCC
starts with the function f
that Haskell programmer has to implement. callCC passes another function k
to f
. As you can see, the implementation of k
is simple but very confusing.
-- sub function extracted from callCC
k :: a -> ContT r m b
k = \ x -> ContT $ \ _ -> c x
In the example above, x
is "hello world". k
simply returns a special suspended computation which ignores following continuation and replace it with c
. So when the Continuation Monad returned by calling k
with "hello world" is bound(i.e, >>=
) with another Continuation Monad : (return "never printed)
, the continuation encapsulated in it will not be executed. Also, running ContT $ \ _ -> c x
with another Continuation will lead the continuation ignored. So runContT (f (\ x -> ContT $ \ _ -> c x)) c
simply executes c x
.
In function f
, k
may not be called. In this case, body of f
will be executed until the last expression. Then runContT (f (\ x -> ContT $ \ _ -> c x)) c
will pass c
as continuation to the Continuation Monad returned by f
.
Foldable
type of foldMap . foldMap
is (a -> m) -> t1 (t2 a) -> m
. type of foldMap
is (a -> m) -> t2 a -> m
. So you can think of foldMap
as a function which transforms a function of type (a->m)
to type (t2 a -> m)
. So for foldMap . foldMap
, the right foldMap
transforms a->m
to t2 a -> m
, then the left foldMap
transforms t2 a -> m
to t1 (t2 a) -> m
.