Lecture 12: Some fun with kinds

Lecture plan

  • Kinds
  • -XTypeOperators
  • -XDataKinds. Data promotion
  • Heterogeneous lists
  • Type symbols
  • Data and type families
  • Free monads

Idris Effects (just example)

eval : Expr -> Eff Integer [STDIO, EXCEPTION String, RND, STATE Env]
data Expr = Val Integer
          | Var String
          | Add Expr Expr
          | Random Integer

More expressive, hah?

 

Idris rulit' i razrulivaet?

Data type

Evaluation function

Idris-like effects in Haskell

data RandomGen = RandomGen
data WriteLn = WriteLn { unWriteLn :: String }

type instance EffectRes RandomGen = Int
type instance EffectRes WriteLn = ()

myExecution :: Free (MyF '[ RandomGen , WriteLn ]) Int
myExecution = do
  rand1 <- effect RandomGen
  rand2 <- effect RandomGen
  effect $ WriteLn $ "Generated two random numbers: "
    <> show rand1 <> " " <> show rand2
  pure $ rand1 ^ 2 + rand2

runExecution :: IO Int
runExecution = iterM (runMyF handlers) myExecution
  where
    handlers = Handler (const randomIO) :& Handler (putStrLn . unWriteLn) :& RNil

Haskell allows you to write exactly same code.

Full code uses free monads, DataKinds and other advanced machinery. To be continued on next lectures.

Now!

Types of types

ghci> :kind Int
Int :: *
ghci> :kind Char
Char :: *
ghci> :kind Maybe
Maybe :: * -> *
ghci> :kind Maybe String
Maybe :: *
ghci> :kind (Maybe Maybe)  -- ??
data MapTree k v 
  = Leaf 
  | Node k v (MapTree k v) (MapTree k v)

MapTree          :: * -> * -> *
MapTree a        :: * -> *
MapTree String v :: *

In FP everything is a function. So you think of types as of functions. And types also can be partially applied.

List type

ghci> :info []
data [] a = [] | a : [a] 	-- Defined in ‘GHC.Types’
...
ghci> :kind []
[] :: * -> *                    -- kind of list
ghci> :kind [] Int
[] Int :: *                     -- `[] a` is the same as [a]
ghci> :kind [String]
[String] :: *

Arrow type

ghci> :info (->)
data (->) t1 t2 	-- Defined in ‘GHC.Prim’
infixr 0 `(->)`
...
ghci> :k (->)
(->) :: * -> * -> *  -- kind of function
ghci> :k (->) Int
(->) Int :: * -> *     

-- ((->) Int) is the same as (Int -> )

Why care about kinds?

newtype ReaderT r (m :: * -> *) (a :: *) = ReaderT {runReaderT :: r -> m a}

ghci> :kind ReaderT
ReaderT :: * -> (* -> *) -> * -> *

Your types can be parametrized by higher-kinded types!

ghci> :kind ReaderT String
ReaderT String :: (* -> *) -> * -> *

ghci> :kind ReaderT String IO
ReaderT String IO :: * -> *

ghci :kind ReaderT String IO Int
ReaderT String IO Int :: *
ghci> :kind ReaderT String Int

<interactive>:1:16: error:
    • Expected kind ‘* -> *’, but ‘Int’ has kind ‘*’
    • In the second argument of ‘ReaderT’, namely ‘Int’
      In the type ‘ReaderT String Int’

What if I substitute type of wrong type?

instance Monad m => Monad (ReaderT r m)
class Monad (m :: * -> *) where
  return :: a -> m a
  (>>=)  :: m a -> (a -> m b) -> m b

Declassified kind

class Num a where ...
ghci> :kind Num
??

Hmmm, what if...

ghci> :kind Num
Num :: * -> Constraint
{-# LANGUAGE ConstraintKinds #-}

type MyConstraints a = (Read a, Num a, Show a)

foo :: MyConstraints a => String -> a -> a

You can create aliases for constraints!

ghci> :set -XRankNTypes 
ghci> type ConstraintEndomorphism p a = p a => a -> a
ghci> :kind ConstraintEndomorphism 
ConstraintEndomorphism :: (* -> Constraint) -> * -> *

And much more

ghci> data a * b = Mult a b

    Illegal declaration of a type or class operator ‘*’
      Use TypeOperators to declare operators in type and declarations

-XTypeOperators

You can't normally declare type names as operators

ghci> :set -XTypeOperators 
ghci> data a * b = Mult a b
ghci> :t (*)
(*) :: Num a => a -> a -> a
ghci> :k (*)
(*) :: * -> * -> *
ghci> let x = Mult @Int 3 True
ghci> :t x 
x :: Int * Bool
-- Defined at Data.Type.Equality
data a :~: b where
    Refl :: a :~: a

Kind grammar

* is a kind

For kinds a and b​,

(a -> b) is a kind

Constraint is a kind

And if for any term v following holds: v :: (t :: *)

And every constraint T has kind T :: *

Data types are of kind * or k -> *

Type classes are of kind Constraint or k -> Constraint

Any other kinds?

Oh yeeeah!

data Z
data S n

data Vec :: * -> * -> * where
    Nil  :: Vec a Z
    Cons :: a -> Vec a n -> Vec a (S n)

Datatype promotion

Motivation example

v2 :: Vec Int (S (S Z))
v2 = 1 `Cons` (2 `Cons` Nil)
{-# LANGUAGE DataKinds #-}

data Nat = Z | S Nat

data Vec :: * -> Nat -> * where
    Nil  :: Vec a Z
    Cons :: a -> Vec a n -> Vec a (S n)
v3 :: Vec Int Char
v3 = ??

By construction we know that there can't be a value of type Vec Int Char except for

But how to tell this to compiler?

* is an open universe of types

We defined our own custom closed universe :)

data Nat = Zero | Succ Nat
ghci> :t Succ
Succ :: Nat -> Nat
ghci> :k Nat
Nat :: *
ghci> :k Succ
    Not in scope: type constructor or class ‘Succ’
    A data constructor of that name is in scope; did you mean DataKinds?
ghci> :set -XDataKinds 
ghci> :k Succ
Succ :: Nat -> Nat
ghci> type Two = Succ (Succ Zero)
ghci> :k Two
Two :: Nat
ghci> type Two = 'Succ ('Succ 'Zero) -- due to ambiguity of type and constructor
ghci> :k Two
Two :: Nat
ghci> :k '[]
'[] :: [k]
ghci> :k '[Int, Bool]
'[Int, Bool] :: [*]
ghci> :k '[Maybe, Either String]
'[Maybe, Either String] :: [* -> *]
ghci> :k (Int ': '[Bool])
(Int ': '[Bool]) :: [*]

-XDataKinds

TypeSafe Vectors

data Vec :: * -> Nat -> * where
    Nil  :: Vec a 'Z
    (:>) :: a -> Vec a n -> Vec a ('S n)
zipV :: Vec a n -> Vec b n -> Vec (a, b) n
zipV       Nil       Nil = Nil
zipV (x :> xs) (y :> ys) = (x, y) :> zipV xs ys
ghci> let x = 3    :> Nil
ghci> let y = True :> Nil
ghci> :t x
x :: Num a => Vec a ('S 'Z)
ghci> :t y
y :: Vec Bool ('S 'Z)
ghci> :t zipV x y
zipV x y :: Num a => Vec (a, Bool) ('S 'Z)
ghci> :t zipV x (4 :> y)
    • Couldn't match type ‘'S 'Z’ with ‘'Z’
      Expected type: Vec Bool ('S 'Z)
        Actual type: Vec Bool ('S ('S 'Z))
    • In the second argument of ‘zipV’, namely ‘(4 :> y)’
      In the expression: zipV x (4 :> y)

Heterogeneous lists

data HList :: [*] -> * where
    HNil :: HList '[]
    (:^) :: a -> HList t -> HList (a ': t)

infixr 2 :^
foo0 :: HList '[]
foo0 = HNil

foo1 :: HList '[Int]
foo1 = 3 :^ HNil

foo2 :: HList '[Int, Bool]
foo2 = 5 :^ False :^ HNil
instance Show (HList '[]) where
    show _ = "H[]"
instance (Show e, Show (HList l)) => Show (HList (e ': l)) where
    show (x :^ l) = let 'H':'[':s = show l
                    in "H[" ++ show x ++ (if s == "]" then s else ", " ++ s)
ghci> foo0
H[]
ghci> foo2
H[5, False]

Type Level Symbols

data Vec :: * -> Nat -> * where
    Nil  :: Vec a 0
    (:>) :: a -> Vec a n -> Vec a (n + 1)
ghci> :t True :> False :> Nil
True :> False :> Nil :: Vec Bool 2
newtype ArrPtr (n :: Nat) a = ArrPtr (Ptr a)

clearPage :: ArrPtr 4096 Word8 -> IO ()
clearPage (ArrPtr p) = ...
import GHC.TypeLits

Built-in type-level natural numbers and strings :)

ghci> :t (+)
(+) :: Num a => a -> a -> a
ghci> :k (+)
(+) :: Nat -> Nat -> Nat
ghci> :i +
class Num a where
  ...
infixl 6 +

type family (+) (a :: Nat) (b :: Nat) :: Nat
infixl 6 +

Wait a minute!

What is n + 1?

Type-level functions

newtype Foo bar = MkFoo { unFoo :: bar }
MkFoo :: bar -> Foo bar    (term level)
Foo :: * -> *              (type level)

Foo is a type level function which takes type bar and returns type Foo bar

data Foo a where
  Foo :: Int -> Foo Int
  Bar :: Char -> Foo Double
Foo :: Int -> Foo Int      (term level)
Bar :: Char -> Foo Double  (term level)
Foo :: * -> *              (type level)

Foo is a type level function which takes type a and depending on it returns Foo Int or Foo Double

Can I write code like this?

type fun Foo a where
  | a == Char = Double
  | otherwise = a

Type and data families

type family Foo bar :: * where
  Foo Char = Double
  Foo b = b

Foo is a type level function which takes type bar and returns either Double or bar

Recommend reading GHC docs on type and data families.

type family Foo bar :: *
type instance Foo Char = Double
type instance Foo Int = Int

Type families may be open :)

type family Foo bar :: *
type instance Foo Char = Double
type instance Foo a = a

->

error:
  Conflicting family instance declarations
data family Foo bar :: *
data instance Foo Int = MkFoo Int

Top-level data families are like open GADTs.

More family business

class Foo p where
  type AType p :: *
  data BType p :: *

  make :: AType p -> BType p

instance Foo Int where
  type AType Int = Int
  data BType Int = B Integer
  
  make = B . toInteger

Alternative syntax for use with type classes.

Primary use case for data families.



type family FromMaybe
     (m :: Maybe *) (def :: *) :: * where
  FromMaybe ('Just t) def = t
  FromMaybe 'Nothing def = def

Type families + data kinds

FromMaybe ('Just Int) Bool ~ Int
{-# LANGUAGE PolyKinds #-}

type family FromMaybe
     (m :: Maybe k) (def :: k) :: k where
  FromMaybe ('Just t) def = t
  FromMaybe 'Nothing def = def

+ poly kinds     

Type family limitations

show' :: Show (Foo a)
      => Foo a -> String
show' = show


type family Foo bar :: * where
  Foo Char = Double
  Foo b = b
error:
  • Couldn't match type ‘Foo a0’ with ‘Foo a’
    Expected type: Foo a -> String
    Actual type: Foo a0 -> String
    NB: ‘Foo’ is a non-injective type family
{-# LANGUAGE TypeFamilyDependencies #-}

type family Foo a = r | r -> a where
  Foo Char = Double
  Foo a = a
error:
  • Type family equations violate injectivity:
    Foo Char = Double
    Foo a = a
{-# LANGUAGE TypeFamilyDependencies #-}

type family Foo a = r | r -> a where
  Foo Char = Double
  Foo Int = Int
ghci> show' (1 :: Int)
"1"

ghci> (show' :: _) (1 :: Double)
 • Found type wildcard for ‘Foo Char -> String’

Other options to workaround non-injective type families:

  • Use data families
  • Use type family wrapped into a newtype
type family Foo bar :: *

newtype FooWrapped bar = Foo { unFoo :: Foo bar }

Some advanced data types

data Union (f :: u -> *) (as :: [u]) where
  This :: !(f a) -> Union f (a ': as)
  That :: !(Union f as) -> Union f (a ': as)

Type Union from union package.

data Rec :: (u -> *) -> [u] -> * where
  RNil :: Rec f '[]
  (:&) :: !(f r) -> !(Rec f rs) -> Rec f (r ': rs)

Type Rec from vinyl package.

Rec is a generalized pair, while Union is a generalized Either.

Let's see how to use these types:

one3 :: Union Identity '[Int, Char, Int]
one3 = That (This (Identity 'a'))
tup3 :: Rec Identity '[Int, Char, Int]
tup3 = Identity 1 :& Identity 'a'
                  :& Identity 5 :& RNil
tup3 :: Rec Maybe '[Int, Char, Int]
tup3 = Just 1 :& Nothing
                  :& Just 5 :& RNil

You can use any type-level functor f :: * -> *

Free monads

data Free f a = Pure a | Free (f (Free f a))

Free monad is an abstraction of multistep computation where each subsequent step requires some input from the outer context to continue.

data Action a =
  PrintLn String a | ReadInt (Int -> a)
  

justPrint :: Free Action Int
justPrint = Free $ PrintLn "poid" (Pure 5)

readIncPrint :: Free Action ()
readIncPrint =
  Free $ ReadInt $ \i ->
    Free $ PrintLn (show $ i + 1) (Pure ())
runAction :: Free Action a -> IO a
runAction (Pure x) = pure x
runAction (Free (PrintLn s cont)) =
  putStrLn s *> runAction cont
runAction (Free (ReadInt cont)) = do
  i <- fmap read getLine
  runAction (cont i)
ghci> runAction readIncPrint
5
6

But why is it called a monad?

What is free here?

Monad instance for free

data Free f a = Pure a | Free (f (Free f a))
instance Functor f => Monad (Free f) where
  return = pure

  Pure a >>= f = f a
  Free m >>= f = Free ((>>= f) <$> m)

Monad instance is declared for every functor f !

Yummy :))

data Action a =
  PrintLn String a | ReadInt (Int -> a)
  deriving Functor

printLn :: String -> Free Action ()
printLn s = Free $ PrintLn s (Pure ())

readInt :: Free Action Int
readInt = Free $ ReadInt Pure

readIncPrint :: Free Action ()
readIncPrint = do
  i <- readInt
  printLn $ show (i + 1)

Is Free really for free?

  • >>= is O(d) for d being a depth of computation
  • Interpreting step-by-step is much worse than compilation
  • You can not formalize something with two continuations, like concurrently

Still, Free is quite useful for prototyping :)

And there are similar concepts overcoming aforementioned problems.

Idris-like effects: types

type family EffectRes s :: *

data MyFEl a s = MyFEl s (EffectRes s -> a)

newtype MyF xs a = MyF ( Union (MyFEl a) xs)

instance Functor (MyF xs) where (..)

effect
  :: (UElem s xs (RIndex s xs))
  => s -> Free (MyF xs) (EffectRes s)
effect = Free . MyF . ulift . flip MyFEl pure

newtype Handler m s = Handler ( s -> m (EffectRes s) )

runMyF
  :: Monad m
  => Rec (Handler m) xs -> MyF xs (m a) -> m a
runMyF (Handler h1 :& hRest) (MyF u) =
  union (runMyF hRest . MyF) handle u
  where
    handle (MyFEl e resH) = resH =<< h1 e
runMyF RNil (MyF u) = absurdUnion u

Idris-like effects: again

data RandomGen = RandomGen
data WriteLn = WriteLn { unWriteLn :: String }

type instance EffectRes RandomGen = Int
type instance EffectRes WriteLn = ()

myExecution :: Free (MyF '[ RandomGen , WriteLn ]) Int
myExecution = do
  rand1 <- effect RandomGen
  rand2 <- effect RandomGen
  effect $ WriteLn $ "Generated two random numbers: "
    <> show rand1 <> " " <> show rand2
  pure $ rand1 ^ 2 + rand2

runExecution :: IO Int
runExecution = iterM (runMyF handlers) myExecution
  where
    handlers = Handler (const randomIO) :& Handler (putStrLn . unWriteLn) :& RNil

Haskell allows you to write exactly same code.

Full code is still worth to be checked :)

Promoted Literature for free

Lecture 12: Some fun with kinds

By ITMO CTD Haskell

Lecture 12: Some fun with kinds

Lecture about kinds, -XDataKinds, free monads etc.

  • 3,372