Hao Wu / Field Notes

Typeclass Overview

Readings

Recap

Parametric Types

Every piece of information matters in its own way.

  1. Type Constructor defines global representation of a type.
  2. Value/Data Constructor defines local representation of a type
  3. Type Constructor may has
    • Zero argument (a):
      • global representation represents the collections of local representations
      • local representation carries all information of this type. For Pattern Match.
      • eg: data Bool = True | False
        • global representation is Bool.
        • local representations are True & False.
    • One argument (T a):
      • global representation represents the collection of local representations
        • a : represents the Target Type.
        • T : represents the Computational Context / Context Type.
      • local representation carries all information of this type. For Pattern Match.
      • eg: data Maybe a = Just a | Nothing
        • global representation is Maybe a.
          • a is the Target Type
          • Computational Context is Maybe
        • local representations are Just a & Nothing.
    • More than one argument (T a b):
      • global representation represents the collection of local representations
        • a : represents the Target Type.
        • b : represents the Target Type.
        • T : represents the Computational Context / Context Type.
      • local representation carries all information of this type. For Pattern Match.
      • eg: data Either a b = Left a | Right b
        • global representation is Either a b.
          • a is the Target Type
          • b is the Target Type
          • Computational Context is Either
        • local representations are Left a & Right b.
Example:

data Tree a = Tip | Node a (Tree a) (Tree a)

For convenient

  • Value Constructor == Data Constructor
  • Computational Context == Context Type

. One Target Type at A Time

Thanks to the Currying , Type Constructor could be parametrized. Usually, and in this doc, the computational chain (function composition) focus on the transform of target types. Each computation focus on One target type at a time.

For Parametric types with More than One parameters, we focus on the rightmost one :

eg: b in data Either a b = Left a | Right b

Parametric types is the foundation of decomposing an application into computational chain and computational context factors.

A functional application is just a recomposition of computational chains of different computational context.

Category theory and haskell guarantee that the behaviour of recomposing a long computation chain would be predicable.

Predictable does not implies good implementation. There are design principles and design patterns for functional programming that we need to follow for achieving scalability and maintainability.

Section 0. Computational Context

Parametric type X Typeclasses = Computational Context

Three common Typeclasses, that being used to define how, a context type f, could affect the target computation a -> b.

typeclass main-function function type
Functor <$> (a -> b) -> f a -> f b
Applicative <*> f(a -> b) -> f a -> f b
Monad >>= f a -> ( a -> f b) -> f b

Flip the argument of >>=, we get (a-> f b) -> f a -> f b. Now it is much clear. These function types could be seen as two parts. The first part includes:

  1. (a -> b), f (a -> b), a -> f b

The second part of these three functions are all the same:

  1. f a - f b

Intuitively, these three functions are about how to transform different mapping relations (in 1) into context sensitive mapping f a -> f b. So that we could compose functions of the same Context Semantics.

When f a is a certain Parametric Type, it could indicate a specific Computational Context with a specific Context Semantics.

|Parametric types:| List | Product |Sum | Exponential |
|:–|:–|:–:|:–:|:–:|:–:| |<$>| Container | Container |Container |Container | |<*>| Generator|Container |Container |Container | |>>=| [] | Context Writer | Context Either/Maybe/IO | Context Reader/State

Summary

<*> :: f (a ->b) -> f a -> f b Context information starts to effect

<$> cannot handle this

>=> :: (a -> f b) -> (b -> fc) -> (a -> fc)

<*> cannot provide this composition ability

>>= where the Context information starts to shine

example of applicativeDo

would normally be desugared to foo1 >>= \x -> foo2 >>= \y -> foo3 >>= \z -> return (g x y z), but this is equivalent to g <$> foo1 <*> foo2 <*> foo3. With the ApplicativeDo extension enabled (as of GHC 8.0), GHC tries hard to desugar do-blocks using Applicative operations wherever possible. This can sometimes lead to efficiency gains, even for types which also have Monad instances, since in general Applicative computations may be run in parallel, whereas monadic ones may not. For example, consider

g :: Int -> Int -> M Int

-- These could be expensive
bar, baz :: M Int

foo :: M Int
foo = do
  x <- bar
  y <- baz
  g x y
foo definitely depends on the Monad instance of M, since the effects generated by the whole computation may depend (via g) on the Int outputs of bar and baz. Nonetheless, with ApplicativeDo enabled, foo can be desugared as

join (g <$> bar <*> baz)
which may allow bar and baz to be computed in parallel, since they at least do not depend on each other.

The ApplicativeDo extension is described in this wiki page, and in more detail in this Haskell Symposium paper.

Parametric types + Typeclass = Intuitive semantics

MonadTrans

T m a

newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }
instance (Monad m) => Monad (StateT s m) where
#if !(MIN_VERSION_base(4,8,0))
    return a = StateT $ \ s -> return (a, s)
    {-# INLINE return #-}
#endif
    m >>= k  = StateT $ \ s -> do
        ~(a, s') <- runStateT m s
        runStateT (k a) s'
    {-# INLINE (>>=) #-}
    fail str = StateT $ \ _ -> fail str
    {-# INLINE fail #-}
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

instance (Monad m) => Monad (MaybeT m) where
    fail _ = MaybeT (return Nothing)
    return = lift . return
    x >>= f = MaybeT $ do
        v <- runMaybeT x
        case v of
            Nothing -> return Nothing
            Just y  -> runMaybeT (f y)

For example, when a StateT s Maybe a computation fails, the state ceases being updated (indeed, it simply disappears); on the other hand, the state of a MaybeT (State s) a computation may continue to be modified even after the computation has “failed”. This may seem backwards, but it is correct. [haskell wiki:typeclass]

Common usage

TODO:

Section 1. Transform within the same type

Semigroup and Monoid represent type :: a and an operation :: a -> a -> a on that type.

Readings

1. Semigroup Data.Semigroup

Define binary function <> that take input and give output of the same type.

class Semigroup a where
  (<>) :: a -> a -> a

  default (<>) :: Monoid a => a -> a -> a
  (<>) = mappend

2. Monoid

Define mempty :: a that enabling (<> mempty) to be an identity function on type a.

class Semigroup a => Monoid a where
        -- | Identity of 'mappend'
        mempty  :: a

        -- | An associative operation
        --
        -- __NOTE__: This method is redundant and has the default
        -- implementation @'mappend' = '(<>)'@ since /base-4.11.0.0/.
        mappend :: a -> a -> a
        mappend = (<>)
        {-# INLINE mappend #-}

        -- | Fold a list using the monoid.
        --
        -- For most types, the default definition for 'mconcat' will be
        -- used, but the function is included in the class definition so
        -- that an optimized version can be provided for specific types.
        mconcat :: [a] -> a
        mconcat = foldr mappend mempty

Section 2: Monoidal subclass

  1. In a monoidal operation :: t -> t -> t, t could be a parametric type.
Typeclass List product Sum ->
`< >` :: f a-> f a -> f a Container Container Container
mplus:: m a-> m a -> m a` Container Container Container Container
Maybe Example:
instance (Functor m, Monad m) => Alternative (MaybeT m) where
    empty = mzero
    (<|>) = mplus

instance (Monad m) => MonadPlus (MaybeT m) where
    mzero = MaybeT (return Nothing)
    mplus x y = MaybeT $ do
        v <- runMaybeT x
        case v of
            Nothing -> runMaybeT y
            Just _  -> return v

Reader Example:

instance (Alternative m) => Alternative (ReaderT r m) where
    empty   = liftReaderT empty
    {-# INLINE empty #-}
    m <|> n = ReaderT $ \ r -> runReaderT m r <|> runReaderT n r
    {-# INLINE (<|>) #-}

instance (MonadPlus m) => MonadPlus (ReaderT r m) where
    mzero       = lift mzero
    {-# INLINE mzero #-}
    m `mplus` n = ReaderT $ \ r -> runReaderT m r `mplus` runReaderT n r
    {-# INLINE mplus #-}

Writer Example:

instance (Monoid w, Alternative m) => Alternative (WriterT w m) where
    empty   = WriterT empty
    {-# INLINE empty #-}
    m <|> n = WriterT $ runWriterT m <|> runWriterT n
    {-# INLINE (<|>) #-}

instance (Monoid w, MonadPlus m) => MonadPlus (WriterT w m) where
    mzero       = WriterT mzero
    {-# INLINE mzero #-}
    m `mplus` n = WriterT $ runWriterT m `mplus` runWriterT n
    {-# INLINE mplus #-}

Several classes have monoidal subclass to model computation that support failure or choice.

Section 3: Utility Functions

3.0.Content

:: (Functor t, Foldable t, Traversable t) =>

on monads type :: Monad m => on Applicative :: Applicative f type :: Applicative f =>
return a -> m a pure a -> f a
liftM2 (a -> b -> c) -> m a -> m b -> m c liftA2 ( a -> b -> c) -> f a -> f b -> f c
mapM (a -> m b) -> t a -> m (t b) traverse (a -> f b) -> t a -> f (t b)
forM t a -> (a -> m b) -> m (t b) for t a -> (a -> f b) -> f (t b)
sequence t (m a) -> m ( t b) sequenceA t (f b) -> f ()
mapM_ (a -> m b) -> t a -> m () traverse (a -> f b) -> t a -> f ()
forM_ t a -> (a -> m b) -> m () for t a -> (a -> f b) -> f ()
sequence_ t (m a) -> m () sequenceA t (f b) -> f ()

3.1.Applicative and Monad

|function| constraint|type| define | import |
|:–:|:–:|:–:|:–:|:–:|:–:| |liftA2| Applicative f => | (a -> b -> c) -> (f a -> f b -> f c)| Control.Applicative | GHC.Base| |liftM2| Monad m => | (a -> b -> c) -> (m a -> m b -> m c)| Control.Monad | GHC.Base|

3.2.Foldable

|function| constraint|type| define | import |
|:–:|:–:|:–:|:–:|:–:|:–:| |foldMap| Monoid m, Foldable t| (a -> m) -> t a -> m| Data.Foldable | GHC.Base| |fold| Monoid m, Foldable t| t a -> m| Data.Foldable | Data.Foldable| |foldrM| Monad m, Foldable t| (a -> b -> m b) -> b -> t a -> m b| Data.Foldable | Data.Foldable| |foldlM = foldM| Monad m, Foldable t| (b -> a -> m b) -> b -> t a -> m b| Data.Foldable | Data.Foldable|

3.3.Traversable

function constraint type define import
traverse Applicative f, (Functor, Foldable, Traversable t) (a -> f b) -> t b -> f (t b) Data.Traversable Prelude
mapM Monad m, (Functor, Foldable, Traversable t) (a -> m b) -> m b -> m (t b) Data.Traversable Prelude
function constraint type define import
for Applicative f, (Functor, Foldable, Traversable t) t b -> (a -> f b)-> f (t b) Data.Traversable Data.Traversable
forM Monad m, (Functor, Foldable, Traversable t) m b -> (a -> m b)-> m (t b) Data.Traversable Prelude
function constraint type define import
sequenceA Applicative f, (Functor, Foldable, Traversable t) t (f a) -> f (t a) Data.Traversable Prelude
sequence Monad m, (Functor, Foldable, Traversable t) t (m a) -> m (t a) Data.Traversable GHC.Base

“underscored” variants, such as sequence_ and mapM_; these variants throw away the results of the computations passed to them as arguments, using them only for their side effects.

monad constrained type define import
mapM_ (Foldable t, Monad m) => (a -> m b) -> t a -> m () Data.Foldable Prelude
forM_ (Foldable t, Monad m) => t a -> (a -> m b) -> m () Data.Foldable Data.Foldable
sequence_ (Foldable t, Monad m) => t (m a) -> m () Data.Foldable Prelude
Applicative constrained type define import
traverse_ (Foldable t, Applicative f) => (a -> f b) -> t a -> f () Data.Foldable Data.Foldable
for_ (Foldable t, Applicative f) => t a -> (a -> f b) -> f () Data.Foldable Data.Foldable
sequenceA_ (Foldable t, Applicative f) => t (f a) -> f () Data.Foldable Data.Foldable
  • This so call side effects is the what these functions all about.
  • All these functions are defined in Data.Foldable.
  • mapM_ and sequence_ are exposed by Prelude.
  • TODO: needs more attention and example.

3.4. newtype

Mileski twitter example

type ListPair a = [(Card,a)]
instance Functor ListPair where 
  fmap f = fmap (bimap id f)

> The type synonym 'ListPair' should have one argument but has been given one.
> In the instance declaration for 'Functor ListPair'

The correct definition should be :

newtype ListPair a = LP {unLP :: [(Card,a)]}
instance Functor ListPair where 
  fmap f = fmap (bimap id f)

Then wrap and unwrap value of type ListPair explicitly.

TODO

Section 4

##TODO

TODO

newtype Mileski twitter example

type ListPair a = [(Card,a)]
instance Functor ListPair where 
  fmap f = fmap (bimap id f)

> The type synonym 'ListPair' should have one argument but has been given one.
> In the instance declaration for 'Functor ListPair'

The error free form should be

newtype ListPair a = LP {unLP :: [(Card,a)]}
instance Functor ListPair where 
  fmap f = fmap (bimap id f)

Then wrap and unwrap value of type ListPair explicitly.