Let’s say you wanted to convert pairs to lists of Strings
pairToStringList :: (Show a, Show b) => (a, b) -> [String]
pairToStringList (a, b) = [show a, show b]
*Main> pairToStringList (True, Just 3)
["True","Just 3"]
Now say you want to convert a pair of Enum
s to a list of Int
s
pairToIntList :: (Enum a, Enum b) => (a, b) -> [Int]
pairToIntList (a, b) = [fromEnum a, fromEnum b]
Can we generalize this function? Would like to say:
pairToList conv (a, b) = [conv a, conv b]
pairToList show (True, Just 3) -- error
Unfortunately, can’t pass methods as arguments, only functions
pairToList :: (a -> b) -> (a, a) -> [b]
Let’s represent ad hoc polymorphic methods with a class
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
class Function f a b | f a -> b where
funcall :: f -> a -> b
instance Function (a -> b) a b where
funcall = id
pairToList :: (Function f a c, Function f b c) =>
f -> (a, b) -> [c]
pairToList f (a, b) = [funcall f a, funcall f b]
Use placeholder singleton (“unit”) types to represent particular methods
data ShowF = ShowF
instance (Show a) => Function ShowF a [Char] where
funcall _ = show
data FromEnumF = FromEnumF
instance (Enum a) => Function FromEnumF a Int where
funcall _ = fromEnum
Function
in actionNow singleton types act like method arguments:
*Main> pairToList ShowF (True, 3)
["True","3"]
*Main> pairToList FromEnumF (False, 7)
[0,7]
tupleToList
for arbitrary n-tuples?
class TupleFoldr f z t r | f z t -> r where
tupleFoldr :: f -> z -> t -> r
-fcontext-stack
argumentDeriveDataTypeable
extensionShow
, Read
, Eq
, Ord
, Bounded
, Enum
DeriveDataTypeable
extension adds two more: Typeable
, Data
data MyType = Con1 Int | Con2 String deriving (Typeable, Data)
Data
requires that inner types (Int
, String
) also have instancesTypeable
requires any type parameters to have instances-- MyTyCon only typeable when a is
data MyTyCon a = MyTyCon a deriving (Typeable, Data)
Typeable
and Data
instancesTypeable
classimport Data.Typeable
to get Typeable
class:
class Typeable a where
typeOf :: a -> TypeRep -- Note: never evaluates argument
data TypeRep -- Opaque, but instance of Eq, Ord, Show, Typeable
This allows us to compare types for equality
rtTypeEq :: (Typeable a, Typeable b) => a -> b -> Bool
rtTypeEq a b = typeOf a == typeOf b
*Main> rtTypeEq True False
True
*Main> rtTypeEq True 5
False
OverlappingInstances
?GHC has a function unsafeCoerce
unsafeCoerce :: a -> b
Let’s use Typeable
to make a safe cast
function
cast :: (Typeable a, Typeable b) => a -> Maybe b
cast a = fix $ \ ~(Just b) -> if typeOf a == typeOf b
then Just $ unsafeCoerce a
else Nothing
*Main> cast "hello" :: Maybe String
Just "hello"
*Main> cast "hello" :: Maybe Int
Nothing
typeOf
on two different types always returns different TypeRep
sderiving (Typeable)
; SafeHaskell disallows manual instancesTo cast monadic computations, etc., use generalized cast, gcast
:
import Data.Maybe (fromJust)
gcast :: (Typeable a, Typeable b) => c a -> Maybe (c b)
gcast ca = mcr
where mcr = if typeOf (unc ca) == typeOf (unc $ fromJust mcr)
then Just $ unsafeCoerce ca
else Nothing
unc :: c x -> x
unc = undefined
*Main> fromJust $ gcast (readFile "/etc/issue") :: IO String
"\nArch Linux \\r (\\n) (\\l)\n\n"
*Main> fromJust $ gcast (readFile "/etc/issue") :: IO Int
*** Exception: Maybe.fromJust: Nothing
unc
in definition of gcast
typeOf
is not strictTypeable b =>
is like a hidden argument; often use undefined functions with type signatures to unpack types and get dictionariesTypeable
: mkT
[Boilerplate1]mkT
(“make transformation”) behaves like id
except on one type
mkT :: (Typeable a, Typeable b) => (b -> b) -> a -> a
Example:
newtype Salary = Salary Double deriving (Show, Data, Typeable)
raiseSalary :: (Typeable a) => a -> a
raiseSalary = mkT $ \(Salary s) -> Salary (s * 1.04)
*Main> raiseSalary ()
()
*Main> raiseSalary 7
7
*Main> raiseSalary (Salary 7)
Salary 7.28
mkT
(->)
is Typeable
, so Data.Typeable
exports:instance (Typeable a, Typeable b) => Typeable (a -> b) where ...
mkT :: (Typeable a, Typeable b) => (b -> b) -> a -> a
mkT f a = case cast f of Just g -> g a
Nothing -> a
g
is applied to a
, so must have type a -> a
cast f
must have type Maybe (a -> a)
Typeable
dictionary of (b -> b)
for argument, and dictionary (a -> a)
for return of cast
[Jones] has detailed explanation of Haskell’s type inference
Note, a fancier implementation could use standard maybe
function
maybe :: b -> (a -> b) -> Maybe a -> b
maybe b _ Nothing = b
maybe _ f (Just a) = f a
mkT :: (Typeable a, Typeable b) => (b -> b) -> (a -> a)
mkT f = maybe id id $ cast f
Typeable
: mkQ
[Boilerplate1]Function that computes over one type or returns default val:
mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r
mkQ defaultVal fn a = ...
mkQ
stands for “make query”Example
salaryVal :: Typeable a => a -> Double
salaryVal = mkQ 0 $ \(Salary s) -> s
*Main> salaryVal ()
0.0
*Main> salaryVal 7
0.0
*Main> salaryVal (Salary 7)
7.0
Exercise: implement mkQ
mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r
mkQ defaultVal fn a = case cast a of
Just b -> fn b
Nothing -> defaultVal
mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r
mkQ defaultVal fn = maybe defaultVal fn . cast
extQ
mkQ
only works for one type
mkQ
’s output to work on another type [Boilerplate1]extQ :: (Typeable a, Typeable b) =>
(a -> r) -> (b -> r) -> a -> r
extQ q f a = case cast a of
Just b -> f b
Nothing -> q a
Now can cascade multiple one-type query functions
myShow :: Typeable a => a -> String
myShow = mkQ "unknown type" (show :: Int -> String)
`extQ` (show :: Bool -> String)
`extQ` (show :: Integer -> String)
`extQ` (const "no floating point" :: Double -> String)
infixl 9 `extQ`
)tupleToList
at beginning of lecture if tuples contain limited number of typesExistentialQuantification
extensionLets you introduce type variables on right side of data
declaration
{-# LANGUAGE ExistentialQuantification #-}
data Step s a = Done | Skip !s | Yield !a !s
data Stream a = forall s. Stream (s -> Step s a) !s
Stream a
, there exists a type s
such that…forall
, not exists
, to avoid introducing new keywordControl.Exception
relies on it)Don’t confuse with Rank2Types
, where forall
means for all types s
:
data Stream a = Stream (forall s. s -> Step s a)
Contexts on existential variables like hidden dictionary fields
data Showable = forall a. (Show a) => Showable a
instance Show Showable where
show (Showable a) = "Showable " ++ show a
Showable
value has both a value of type a
, and a dictionary for Show
Data.Dynamic
has type Dynamic
, which can hold anything Typeable
data Dynamic -- opaque type
toDyn :: Typeable a => a -> Dynamic
fromDynamic :: Typeable a => Dynamic -> Maybe a
unsafeCoerce
to coerce everything to a placeholder Obj
typeBut easy to implement safely with ExistentialQuantification
:
data Dynamic = forall a. Typeable a => Dynamic a
toDyn :: Typeable a => a -> Dynamic
toDyn = Dynamic
fromDynamic :: Typeable a => Dynamic -> Maybe a
fromDynamic (Dynamic a) = cast a
GHC runtime implements primitive, unsafe exceptions
raise# :: a -> b
catch# :: IO a -> (b -> IO a) -> IO a -- slight simplification
b
is always same type, otherwise get unsafe coercionControl.Exception
implements safe, hierarchical exceptions
raise#
and catch#
only ever called with one type: SomeException
class (Typeable e, Show e) => Exception e where
toException :: e -> SomeException
toException = SomeException -- default impl
fromException :: SomeException -> Maybe e
fromException (SomeException e) = cast e -- default impl
data SomeException = forall e. Exception e => SomeException e
deriving Typeable -- note use of ExistentialQuantification
instance Exception SomeException where
toException se = se
fromException = Just
instance Show SomeException where
show (SomeException e) = show e
class (Typeable e, Show e) => Exception e where
toException :: e -> SomeException
fromException :: SomeException -> Maybe e
To throw an exception, first convert it to type SomeException
throw :: Exception e => e -> a
throw e = raise# (toException e)
To catch an exception, must ensure it matches desired type
-- Define catchX because catch#'s real type more complicated
catchX :: IO a -> (b -> IO a) -> IO a
catchX (IO a) handler = IO $ catch# a (unIO . handler)
catch :: (Exception e) => IO a -> (e -> IO a) -> IO a
catch action handler = catchX action handler'
where handler' se =
maybe (throwIO se) handler $ fromException se
handler
makes fromException se
use e
’s Exception
dictionaryEasy to add your own top-level exception type
data MyException = MyException deriving (Show, Typeable)
instance Exception MyException -- use default methods
But you can also create a hierarchy of exception types
data AppError = forall e. Exception e => AppError e
deriving (Typeable)
instance Show AppError where show (AppError e) = show e
instance Exception AppError
data Error1 = Error1 deriving (Show, Typeable)
instance Exception Error1 where
toException = toException . AppError
fromException se = do -- using Maybe as a Monad here
AppError e <- fromException se
cast e
-- Now can do the same for Error2, and catch both as AppError
Error1
, or any AppError
Data
classclass Typeable a => Data a where ...
Data
class allows generic traversal and construction of data structures
gfoldl
and gunfold
methods like thisdata T a b = C1 a b | C2 deriving (Typeable, Data)
gfoldl k z (C1 a b) = z C1 `k` a `k` b
gfoldl k z C2 = z C2
toConstr (C1 _ _) = ... -- encodes constructor number
toConstr C2 = ...
gunfold k z c = case constrIndex c of
1 -> k (k (z C1))
2 -> z C2
Data
and Typeable
[fundamental]gfoldl
traversalsThe actual type of gfoldl
:
-- Recall: gfoldl k z (C1 a b) = ((z C1) `k` a) `k` b
gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -- k
-> (forall g. g -> c g) -- z
-> a
-> c a
c
parameter, looks like re-applying constructor
gfoldl ($) id x
, where b
type of partially applied constructorIdentity
monad (applicative functor) around values to ignore c
raiseSalaries :: (Data x) => x -> x
raiseSalaries x = runIdentity $ gfoldl step return (raiseSalary x)
where step cdb d = cdb <*> (pure $ raiseSalaries d)
*Main> raiseSalaries $ Just (1, Salary 4, True, (Salary 7, ()))
Just (1,Salary 4.16,True,(Salary 7.28,()))
gfoldl
queriesCan use a different type c
to ignore constructor/arg types
newtype Q r a = Q { unQ :: r }
qappend :: (Monoid r) => Q r a -> Q r b -> Q r c
qappend (Q r1) (Q r2) = Q $ mappend r1 r2
a
)Now say we want to sum all salaries in a structure
sumSalaries :: (Data x) => x -> Double
sumSalaries x = getSum $ unQ $ gfoldl step (\_ -> toQ x) x
where step tot d = tot `qappend` (Q $ Sum $ sumSalaries d)
toQ = mkQ (Q $ Sum 0) $ \(Salary s) -> Q $ Sum s
-- Uses the following type from Data.Monoid
newtype Sum a = Sum { getSum :: a }
instance Num a => Monoid (Sum a) where
mempty = Sum 0
mappend (Sum a) (Sum b) = Sum (a + b)
*Main> sumSalaries (Salary 7, Salary 9, True, Just (Salary 4))
20.0
Alternative: push generic programming to compile time [Magalhães]
High-level idea: Say you auto-derived instances of Show
-like class:
class MyShow a where myShow :: a -> String
instance MyShow MyType where myShow = genericMyShow
MetaData
class for which compiler can generate instanceclass MetaData d m | d -> m, m -> d where -- not what GHC does
fromData :: d -> m
toData :: m -> d
MyShow
-specific meta-class, such that?class MetaMyShow a where metaMyShow :: a -> String
genericMyShow :: (MetaData d m, MetaMyShow m) => d -> String
genericMyShow = metaMyShow . fromData
DefaultSignatures
extensionWe can do even better using DefaultSignatures
extension
Allows default methods that don’t work for all instances
{-# LANGUAGE DefaultSignatures #-}
class MyShow a where
myShow :: a -> String
default myShow :: (MetaData a m, MetaMyShow m) => a -> String
myShow = genericMyShow
instance MyShow MyType
MetaData
class
DeriveGeneric
extensionCompiler supports single Generic
class that converts any datatype to a Rep
that can be computed over generically:
{-# LANGUAGE TypeFamilies #-}
class Generic a where
type Rep a :: * -> *
from :: a -> Rep a x
to :: Rep a x -> a
type Rep
uses extension called TypeFamilies
. Can read above as:
class Generic a rep | a -> rep where
from :: a -> rep x
to :: rep x -> a
Functor
-like instances)Rep
of a unit type{-# LANGUAGE DeriveGeneric, TypeFamilies, TypeOperators,
FlexibleInstances, FlexibleContexts, UndecidableInstances #-}
import GHC.Generics
data X = X -- because we are dealing with types of kind * -> *
undef2 :: mi c f p -> f p
undef2 _ = undefined
-- A unit type has one constructor and no arguments
data MyType = MyCons deriving (Show, Generic)
*Main> :t from MyCons
from MyCons :: D1 Main.D1MyType (C1 Main.C1_0MyType U1) x
*Main> :t undefined :: Rep MyType X
undefined :: Rep MyType X
:: D1 Main.D1MyType (C1 Main.C1_0MyType U1) X
*Main> datatypeName (from MyCons)
"MyType"
*Main> moduleName (from MyCons)
"Main"
*Main> conName $ undef2 (from MyCons)
"MyCons"
GHC.Generics
contents (part 1){-# LANGUAGE TypeFamilies, KindSignatures, TypeOperators #-}
-- | Unit: used for constructors without arguments
data U1 p = U1
-- | Meta-information (constructor names, etc.)
newtype M1 i c f p = M1 { unM1 :: f p }
-- | Three flavors of meta-information for variable i
data D; type D1 = M1 D -- c instance of Datatype, f is C1 (or :+:)
data C; type C1 = M1 C -- c instance of Constructor, f is S1 (or :*:)
data S; type S1 = M1 S -- c instance of Selector, f is U1 (or Rec0)
class Datatype d where
datatypeName :: t d (f :: * -> *) a -> String
moduleName :: t d (f :: * -> *) a -> String
class Constructor c where
conName :: t c (f :: * -> *) a -> String
class Selector s where
selName :: t s (f :: * -> *) a -> String
data MyType = MyCons deriving (Show, Generic)
data MyType1 = MyCons1 { sel1a :: Bool } deriving (Show, Generic)
data MyType2 = MyCons2 { sel2a :: Bool, sel2b :: Bool } deriving (Show, Generic)
undef2 :: mi c f p -> f p
*Main> :t from MyCons
from MyCons :: D1 Main.D1MyType (C1 Main.C1_0MyType U1) x
*Main> :t from (MyCons1 True)
from (MyCons1 True)
:: D1 Main.D1MyType1
(C1 Main.C1_0MyType1 (S1 Main.S1_0_0MyType1 (Rec0 Bool))) x
*Main> -- was U1 for MyType ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*Main> conName (undef2 $ from $ MyCons1 True)
"MyCons1"
*Main> selName (undef2 $ undef2 $ from $ MyCons1 True)
"sel1a"
*Main> :t (undefined :: Rep MyType2 X)
(undefined :: Rep MyType2 X)
:: D1 Main.D1MyType2
(C1 Main.C1_0MyType2
(S1 Main.S1_0_0MyType2 (Rec0 Bool)
:*: S1 Main.S1_0_1MyType2 (Rec0 Bool)))
X
GHC.Generics
contents (part 2)-- Used to glue multiple constructor arguments together
data (:*:) f g p = f p :*: g p
infixr 6 :*:
-- Used to represent a type with multiple constructors
data (:+:) f g p = L1 { unL1 :: f p } | R1 { unR1 :: g p }
infixr 5 :+:
-- Used to hold actual concrete values of constructor arguments
newtype K1 i c p = K1 { unK1 :: c }
data R
type Rec0 = K1 R
-- From two slides ago:
data U1 p = U1 -- Unit constructors (no arguments)
newtype M1 i c f p = M1 { unM1 :: f p }
data D; type D1 = M1 D -- c instance of Datatype, f is C1 or :+:
data C; type C1 = M1 C -- c instance of Constructor, f is S1 or :*:
data S; type S1 = M1 S -- c instance of Selector, f is U1 or Rec0
p
(there to make types of kind ∗ → ∗)M1
exists so a single traversal method can skip over D1
, C1
, and S1
newtype Rec0 c p = K1 { unK1 :: c }
R
is just left over cruft from older versions that also used K1 P
Generic
instance look like?data T a b = C1 a b | C2 deriving (Show, Generic)
data T_
instance Datatype T_ where
datatypeName _ = "T"
moduleName _ = "Main"
data T_C1_
data T_C2_
instance Constructor T_C1_ where conName _ = "C1"
instance Constructor T_C2_ where conName _ = "C2"
type Rep0T_ a_0 b_1 = D1 T_
(C1 T_C1_ (S1 NoSelector (Rec0 a_0) :*: S1 NoSelector (Rec0 b_1))
:+: (C1 T_C2_ U1))
instance Generic (T a_0 b_1) where
type Rep (T a_0 b_1) = Rep0T_ a_0 b_1
from (C1 f0 f1) = M1 (L1 (M1 (M1 (K1 f0) :*: M1 (K1 f1))))
from (C2) = M1 (R1 (M1 U1))
to (M1 (L1 (M1 (M1 (K1 f0) :*: M1 (K1 f1))))) = C1 f0 f1
to (M1 (R1 (M1 U1))) = C2
Say we are defining our own Show
-like class
class MyShow a where myShow :: a -> String
instance MyShow [Char] where myShow = show
instance MyShow Int where myShow = show
Show1
to deal with annoying p
parameters{-# LANGUAGE FlexibleInstances, UndecidableInstances,
OverlappingInstances, TypeSynonymInstances, TypeOperators,
TypeFamilies, TemplateHaskell, FlexibleContexts #-}
class MyShow1 f where myShow1 :: f p -> String
instance (MyShow1 f) => MyShow1 (M1 i c f) where -- for D1 and S1
myShow1 m1 = myShow1 (unM1 m1)
instance (MyShow1 f, MyShow1 g) => MyShow1 (f :+: g) where
myShow1 (L1 a) = myShow1 a
myShow1 (R1 a) = myShow1 a
MyShow1
When we hit a constructor, want to print the name
instance (Constructor c, MyShow1 f) => MyShow1 (C1 c f) where
myShow1 m1 = conName m1 ++ myShow1 (unM1 m1)
M1
instanceWhen we have no constructor args, don’t show anything
instance MyShow1 U1 where myShow1 _ = ""
When we have multiple constructor args, show them all
instance (MyShow1 f, MyShow1 g) => MyShow1 (f :*: g) where
myShow1 (fp :*: gp) = myShow1 fp ++ myShow1 gp
When you hit the actual value, show it
instance (MyShow c) => MyShow1 (K1 i c) where
myShow1 k1 = ' ' : myShow (unK1 k1)
myShow
, which we haven’t yet defined for many typesMyShow
Now can define generic MyShow
in terms of MyShow1
instance (Generic a, MyShow1 (Rep a)) => MyShow a where
myShow a = myShow1 $ from a
OverlappingInstances
?
D1
, S1
instances of Show1
(easy)myShowDefault
, thenmyShowDefault :: (Generic a, MyShow1 (Rep a)) => a -> String
myShowDefault a = myShow1 $ from a
instance MyShow T1 where myShow = myShowDefault
instance MyShow T2 where myShow = myShowDefault
...
DefaultSignatures
class MyShow a where
myShow :: a -> String
default myShow :: (Generic a, MyShow1 (Rep a)) => a -> String
myShow = myShowDefault
instance MyShow T1
instance MyShow T2
...
[Char]
vs. [a]