Providence Salumu
-X
ExtensionName{-# LANGUAGE
ExtensionName #-}
pragma at top of fileMethod lift
executes actions from underlying transformed monad:
class MonadTrans t where lift :: Monad m => m a -> t m a
Note monads have kind ∗ → ∗, so transformers have kind (∗ → ∗) → ∗ → ∗
StateT
State
from old lecture--can combine, e.g., state and IO
newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }
instance (Monad m) => Monad (StateT s m) where
return a = StateT $ \s -> return (a, s)
m >>= k = StateT $ \s0 -> do -- in monad m
~(a, s1) <- runStateT m s0
runStateT (k a) s1
instance MonadTrans (StateT s) where
lift ma = StateT $ \s -> do -- in monad m
a <- ma
return (a, s)
StateT
Like State
, need actions for getting and setting state
get :: (Monad m) => StateT s m s
get = StateT $ \s -> return (s, s)
put :: (Monad m) => s -> StateT s m ()
put s = StateT $ \_ -> return ((), s)
Example: count lines of standard input
import Control.Exception
import Control.Monad.Trans
import Control.Monad.Trans.State
countLines :: IO Int
countLines = runStateT go 0 >>= return . fst
where go = lift (try getLine) >>= doline
doline (Left (SomeException _)) = get
doline (Right _) = do n <- get; put (n + 1); go
try getLine
is an IO
action, execute with lift
IO
are get
, set
actions from StateT Int IO
monadMonadIO
Recall the MonadIO
class from the iteratee lecture
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
Let's make liftIO
work for StateT
instance (MonadIO m) => MonadIO (StateT s m) where
liftIO = lift . liftIO
Now can execute IO actions without regard to which monad you are in
go = liftIO (try getLine) >>= doline
MonadIO
ContT
, ErrorT
, ListT
, RWST
, ReaderT
, StateT
, WriterT
, ...Top-level, let
, and where
bindings are all recursive in Haskell, e.g.:
oneTwo :: (Int, Int)
oneTwo = (fst y, snd x)
where x = (1, snd y) -- mutual recursion
y = (fst x, 2)
nthFib :: Int -> Integer
nthFib n = fibList !! n
where fibList = 1 : 1 : zipWith (+) fibList (tail fibList)
Function fix
calls a function with its own result, use to re-implement above:
fix :: (a -> a) -> a
fix f = let x = f x in x
oneTwo' :: (Int, Int)
oneTwo' = (fst y, snd x)
where (x, y) = fix $ \ ~(x0, y0) -> let x1 = (1, snd y0)
y1 = (fst x0, 2)
in (x1, y1)
nthFib' n = fibList !! n
where fibList = fix $ \l -> 1 : 1 : zipWith (+) l (tail l)
By contrast, monadic bindings are not recursive
do fibList <- return $ 1 : 1 : zipWith (+) fibList (tail fibList)
... -- error, fibList not in scope ^^^^^^^ ^^^^^^^
But monads in the MonadFix
class have a fixed-point combinator
class Monad m => MonadFix m where
mfix :: (a -> m a) -> m a
mfix
can be used to implement recursive monadic bindings [Erkök00], e.g.:mfib :: (MonadFix m) => Int -> m Integer
mfib n = do
fibList <- mfix $ \l -> return $ 1 : 1 : zipWith (+) l (tail l)
return $ fibList !! n -- ^^^^^
DoRec
extensionrec
keyword introduces recursive bindings in a do
block [Erkök02]
MonadFix
(rec
desugars to mfix
calls)oneTwo'' :: (MonadFix m) => m (Int, Int)
oneTwo'' = do
rec x <- return (1, snd y)
y <- return (fst x, 2)
return (fst y, snd x)
oneTwo''' :: (MonadFix m) => m (Int, Int)
oneTwo''' = do
(x, y) <- mfix $ \ ~(x0, y0) -> do x1 <- return (1, snd y0)
y1 <- return (fst x0, 2)
return (x1, y1)
return (fst y, snd x)
DoRec
helps structure thinking, not shipped that much
mfix
on its own is quite usefulmfix
and rec
Create recursive data structures in one shot
data Link a = Link !a !(MVar (Link a)) -- note ! is okay
mkCycle :: IO (MVar (Link Int))
mkCycle = do
rec l1 <- newMVar $ Link 1 l2 -- but $! would diverge
l2 <- newMVar $ Link 2 l1
return l1
Call non-strict methods of classes (easy access to return-type dictionary)
class MyClass t where
myTypeName :: t -> String -- non-strict in argument
myDefaultValue :: t
instance MyClass Int where
myTypeName _ = "Int"
myDefaultValue = 0
getVal :: (MyClass t) => IO t
getVal = mfix $ \t -> do -- doesn't use mfix's full power
putStrLn $ "Caller wants type " ++ myTypeName t
return myDefaultValue
mfix
Warm-up: The Identity
monad
newtype Identity a = Identity { runIdentity :: a }
instance Monad Identity where
return = Identity
m >>= k = k (runIdentity m)
newtype
compiles to nothing, so basically same as fix
:instance MonadFix Identity where
mfix f = let x = f (runIdentity x) in x
For IO
, mfix = fixIO
(recalling IORef
from laziness lecture):
fixIO :: (a -> IO a) -> IO a
fixIO k = do
ref <- newIORef (throw NonTermination)
ans <- unsafeInterleaveIO (readIORef ref)
result <- k ans
writeIORef ref result
return result
fix
mfix
is not possibleWhat if we tried to define an mfix
-like function for all monads?
mbroken :: (Monad m) => (a -> m a) -> m a -- equivalent to mfix?
mbroken f = fix (>>= f)
mbroken f = mbroken f >>= f
>>=
is strict in its first argument for many monads, so*Main> mfix $ const (return 0)
0
*Main> mbroken $ const (return 0)
*** Exception: stack overflow
mfix
needs to take fixed point over value, not over monadic action
ContT
, ListT
)MonadFix
instance for StateT
What about the StateT
monad?
newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }
instance (Monad m) => Monad (StateT s m) where
return a = StateT $ \s -> return (a, s)
m >>= k = StateT $ \s0 -> do -- in monad m
~(a, s1) <- runStateT m s0
runStateT (k a) s1
rec
notationinstance MonadFix m => MonadFix (StateT s m) where
mfix f = StateT $ \s0 -> do -- in monad m
rec ~(a, s1) <- runStateT (f a) s0 -- ~ redundant?
return (a, s1)
instance MonadFix m => MonadFix (StateT s m) where
mfix f = StateT $ \s -> mfix $ \ ~(a, _) -> runStateT (f a) s
A Haskell 2010 type class declaration can take the form:
class ClassName var where
methodName :: Type {- where type references var -}
class (SuperClass var) => ClassName var where ...
class (Super1 var, Super2 var) => ClassName var where ...
...
var
need not have kind ∗However, the type of each method must mention var
and an implicit (Classname var)
is added to the context of each method, e.g.:
Prelude> :t return
return :: Monad m => a -> m a
A Haskell 2010 instance declaration has the form:
instance [context =>] ClassName (TypeCon v1 ... vk) where ...
v1
... vk
are all variables and all distinct, ruling out, e.g., instance C (a,a)
or instance C (Int a)
or instance [[a]]
MultiParamTypeClasses
extensionEnables type classes with multiple parameters, E.g.:
{-# LANGUAGE MultiParamTypeClasses #-}
class Convert a b where convert :: a -> b
instance Convert Int Bool where convert = (/= 0)
instance Convert Int Integer where convert = toInteger
instance (Convert a b) => Convert [a] [b] where
convert = map convert
Extension itself is relatively safe, but encourages other extensions
E.g., each method's type must use every type parameter
class MyClass a b where
aDefault :: a -- can never use (without more extensions...)
All types (argument and return) must be fully determined
convert 0 :: Bool -- error, 0 has type (Num a) => a
And the usual instance restrictions still apply
instance Convert Int [Char] where convert = show -- error bad param
[Char]
--i.e., ([] Char)
--is not a valid instance parameter, would have to be ([] a)
FlexibleInstances
extension{-# LANGUAGE FlexibleInstances #-}
instance Convert Int [Char] where
convert = show
instance Convert a a where convert a = a
*Main> convert () :: ()
()
*Main> convert ([1,2,3]::[Int]) :: [Integer]
[1,2,3]
*Main> convert ([1,2,3]::[Int]) :: [Int]
<interactive>:1:1:
Overlapping instances for Convert [Int] [Int]
instance Convert a a
instance Convert a b => Convert [a] [b]
OverlappingInstances
extension=>
) not considered when selecting instancesNeed it to do something like built-in Show
instances for String
, []
class MyShow a where myShow :: a -> String
instance MyShow Char where myShow = show
instance MyShow Int where myShow = show
instance MyShow [Char] where myShow = id
instance (MyShow a) => MyShow [a] where
myShow [] = "[]"
myShow (x:xs) = "[" ++ myShow x ++ go xs
where go (y:ys) = "," ++ myShow y ++ go ys
go [] = "]"
So does enabling OverlappingInstances
fix Convert
?
What is the most specific instance?
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverlappingInstances #-}
instance Convert a a where ...
instance (Convert a b) => Convert [a] [b] where ...
*Main> convert ([1,2,3]::[Int]) :: [Int]
<interactive>:1:1:
Overlapping instances for Convert [Int] [Int]
instance [overlap ok] Convert a a
instance [overlap ok] Convert a b => Convert [a] [b]
instance Convert [a] [a] where convert = id
*Main> convert ([1,2,3]::[Int]) :: [Int]
[1,2,3]
OverlappingInstances
module Help where
class MyShow a where
myshow :: a -> String
instance MyShow a => MyShow [a] where
myshow xs = concatMap myshow xs
showHelp :: MyShow a => [a] -> String
showHelp xs = myshow xs -- doesn't see overlapping instance
module Main where
import Help
data T = MkT
instance MyShow T where
myshow x = "Used generic instance"
instance MyShow [T] where
myshow xs = "Used more specific instance"
main = do { print (myshow [MkT]); print (showHelp [MkT]) }
*Main> main
"Used more specific instance"
"Used generic instance"
FlexibleContexts
extensionMultiParamTypeClasses
leads to inexpressible types
toInt val = convert val :: Int
toInt
? Would like to write:toInt :: (Convert a Int) => a -> Int
(Convert a Int) =>
is an illegal context, as Int
not a type variableFlexibleContexts
extension makes the above type legal to write
Each type variable in context must be "reachable" from a type variable in type
(Reachable = explicitly used, or in another constraint with a reachable variable.)
sym :: forall a. Eq a => Int -- illegal
Every constraint must have a type variable
sym :: Eq Int => Bool -- illegal
liftIO
works from so many monads
StateT
? Make get
/set
methods{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
-- Saw this very briefly in Monad lecture:
class (Monad m) => MonadState s m where
get :: m s
put :: s -> m ()
instance (Monad m) => MonadState s (StateT s m) where
get = StateT $ \s -> return (s, s)
put s = StateT $ \_ -> return ((), s)
Now for each other MonadTrans
, pass requests down
liftIO
. E.g., for ReaderT
:instance (MonadIO m) => MonadIO (ReaderT r m) where
liftIO = lift . liftIO
instance (MonadState s m) => MonadState s (ReaderT r m) where
get = lift get
put = lift . put
Remember countLines
?
doline (Left (SomeException _)) = get
doline (Right _) = do n <- get; put (n + 1); go
StateT Int IO
monadget
is StateT Int IO s
s
in order to select an instance of MonadState
!For all compiler knows, might be other matching instances, e.g.,
instance MonadState Double (StateT Int IO) where
-- would be legal, but exists only in compiler's imagination
Since compiler can't infer return type of get
, must type it manually:
doline (Left (SomeException _)) = get :: StateT Int IO Int
doline (Right _) = do n <- get; put ((n :: Int) + 1); go
FunctionalDependencies
extensionOverlappingInstances
)
Lets a class declare some parameters to be functions of others
class (Monad m) => MonadState s m | m -> s where
get :: m s
put :: s -> m ()
| m -> s
" says can select an instance based on m
without considering s
, because s
is a function of m
s
for type inferenceOverlappingInstances
)instance
[context =>
] head [where
body]
The Paterson Conditions: for each assertion in the context
No type variable has more occurrences in the assertion than in the head
class Class a b | a -> b
instance (Class a a) => Class [a] Bool -- bad: 2 * a => 1 * a
instance (Class a b) => Class [a] Bool -- bad: 1 * b => 0 * b
The assertion has fewer constructors and variables than the head
instance (Class a Int) => Class a Integer -- bad: 2 => 2
The Coverage Condition: For each fundep left ->
right, the types in right cannot have type variables not mentioned in left
class Class a b | a -> b
instance Class a (Maybe a) -- ok: a "covered" by left
instance Class Int (Maybe b) -- bad: b not covered
instance Class a (Either a b) -- bad: b not covered
This legal, decidable program will crash your Haskell compiler
crash = f5 ()
where f0 x = (x, x) -- type size 2^{2^0}
f1 x = f0 (f0 x) -- type size 2^{2^1}
f2 x = f1 (f1 x) -- type size 2^{2^2}
f3 x = f2 (f2 x) -- type size 2^{2^3}
f4 x = f3 (f3 x) -- type size 2^{2^4}
f5 x = f4 (f4 x) -- type size 2^{2^5}
UndecidableInstances
extensionFlexibleContexts
when enabledCan increase with -fcontext-stack=
n option, e.g.:
{-# OPTIONS_GHC -fcontext-stack=1024 #-}
{-# LANGUAGE UndecidableInstances #-}
-ftemplate-depth=
optionMonadIO
revisitedRecall definition of MonadIO
class (Monad m) => MonadIO m where
liftIO :: IO a -> m a
instance MonadIO IO where
liftIO = id
Currently must define an instance for every transformer
instance MonadIO (StateT s) where liftIO = lift . liftIO
instance MonadIO (ReaderT t) where liftIO = lift . liftIO
instance MonadIO (WriterT w) where liftIO = lift . liftIO
...
With UndecidableInstances
, one instance can cover all transformers!
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
-- undecidable: assertion Monad (t m) no smaller than head
instance (MonadTrans t, MonadIO m, Monad (t m)) =>
MonadIO (t m) where liftIO = lift . liftIO
We've seen 6 typeclass-related extensions
{-# LANGUAGE MultiParamTypeClasses #-} -- very conservative
{-# LANGUAGE FlexibleInstances #-} -- conservative
{-# LANGUAGE FlexibleContexts #-} -- conservative
{-# LANGUAGE FunctionalDependencies #-} -- frowned upon
{-# LANGUAGE UndecidableInstances #-} -- very frowned upon
{-# LANGUAGE OverlappingInstances #-} -- the most controversial
data Zero = Zero -- Type-level 0
data Succ n = Succ n -- Type-level successor (n + 1)
class NatPlus a b c | a b -> c, a c -> b where
natPlus :: a -> b -> c
natMinus :: c -> a -> b
instance NatPlus Zero a a where
natPlus _ a = a
natMinus a _ = a
-- Note failure of coverage condition below
instance (NatPlus a b c) => NatPlus (Succ a) b (Succ c) where
natPlus (Succ a) b = (Succ (natPlus a b))
natMinus (Succ c) (Succ a) = natMinus c a
NatPlus a b c
, then from types Succ a
and b
we can compute Succ c
(computation at type level)data HFalse = HFalse deriving Show
data HTrue = HTrue deriving Show
class HNot a b | a -> b where hnot :: a -> b
instance HNot HFalse HTrue where hnot _ = HTrue
instance HNot HTrue HFalse where hnot _ = HFalse
class HEven a b | a -> b where hEven :: a -> b
instance HEven Zero HTrue where hEven _ = HTrue
instance (HEven n b, HNot b nb) => HEven (Succ n) nb where
hEven (Succ n) = hnot (hEven n)
*Main> hEven Zero
HTrue
*Main> hEven (Succ Zero)
HFalse
*Main> hEven (Succ (Succ Zero))
HTrue
*Main> hEven (Succ (Succ (Succ Zero)))
HFalse
HNot b nb
to compute negation of b
OverlappingInstances
yet, let's start...Can we compute whether two types are equal? First attempt:
class TypeEq a b c | a b -> c where typeEq :: a -> b -> c
instance TypeEq a a HTrue where typeEq _ _ = HTrue
instance TypeEq a b HFalse where typeEq _ _ = HFalse
TypeEq a a HTrue
not more specific than TypeEq a b HFalse
TypeEq a b c
c
after instance selection with another fundepclass TypeCast a b | a -> b where typeCast :: a -> b
instance TypeCast a a where typeCast = id
instance TypeEq a a HTrue where typeEq _ _ = HTrue -- as before
instance (TypeCast HFalse c) => TypeEq a b c where
typeEq _ _ = typeCast HFalse
TypeEq
TypeEq
is kind of the holy grail of fundeps
TypeEq
, you can distinguish base and recursive casesOverlappingInstances
...Example: Let's do for MonadState
what we did for MonadIO
instance (Monad m) => MonadState s (StateT s m) where
get :: m s
put :: s -> m ()
instance (MonadTrans t, MonadState s m, Monad (t m)) =>
MonadState s (t m) where
get = lift get
put = lift . put
MonadIO
was easier because type IO
can't match parameter (t m)
StateT s m
matches both of above instance headsOverlappingInstances
to select first instance for StateT s m
Last extension: TypeOperators
allows infix types starting with ":
"
data a :*: b = Foo a b
type a :+: b = Either a b
Let's use an infix constructor to define a heterogeneous list
data HNil = HNil deriving Show
data (:*:) h t = h :*: !t deriving Show
infixr 9 :*:
-- Example:
data A = A deriving Show
data B = B deriving Show
data C = C deriving Show
foo = (A, "Hello") :*: (B, 7) :*: (C, 3.0) :*: HNil
*Main> foo
(A,"Hello") :*: ((B,7) :*: ((C,3.0) :*: HNil))
*Main> :t foo
foo :: (A, [Char]) :*: ((B, Integer) :*: ((C, Double) :*: HNil))
Notice our list consisted of pairs
foo :: (A, [Char]) :*: (B, Integer) :*: (C, Double) :*: HNil
foo = (A, "Hello") :*: (B, 7) :*: (C, 3.0) :*: HNil
class Select k h v | k h -> v where
(.!) :: h -> k -> v
instance Select k ((k, v) :*: t) v where
(.!) ((_, v) :*: _) _ = v
instance (Select k h v) => Select k (kv' :*: h) v where
(.!) (kv' :*: h) k = h .! k
*Main> foo .! A
"Hello"
OverlappingInstances
Can use to implement all sorts of other features (concatenation, etc.)
Heterogeneous can implement object-oriented programming!
returnIO :: a -> IO a
returnIO = return
data GetVal = GetVal deriving Show
data SetVal = SetVal deriving Show
data ClearVal = ClearVal deriving Show
mkVal n self = do
val <- newIORef (n :: Int)
returnIO $ (GetVal, readIORef val)
:*: (SetVal, writeIORef val)
:*: (ClearVal, self .! SetVal $ 0)
:*: HNil
test = do -- prints 7, then 0
x <- mfix $ mkVal 7
x .! GetVal >>= print
x .! ClearVal
x .! GetVal >>= print
But why mfix
?
mfix
allows you to override methods with inheritance
SetVal
messagesmkConstVal n self = do
super <- mkVal n self
returnIO $ (SetVal, const $ return ())
:*: super
test2 = do
x <- mfix $ mkConstVal 7
x .! GetVal >>= print
x .! ClearVal
x .! GetVal >>= print
*Main> test
7
0
*Main> test2
7
7
mkVal
's call to SetVal
was properly overridden by mkConstVal