Lecture 3: Side effects, type classes, and monads
Jesper Cockx
2 September 2019
A type class offers one or more functions for a generic type. Examples:
Print A
:
print : A → String
Monoid M
type class:
∅ : M
_+_ : M → M → M
Eq A
:
_==_ : A → A → Bool
and/or _≟_ : (x y : A) -> Dec (x ≡ y)
Functor F
:
fmap : {A B : Set} → F A → F B
Why not have print : {A : Set} -> A -> String
?
Because of parametricity, print
would have to be a constant function.
Type classes allow a different implementation at each type!
A type class is implemented by using a record type + instance arguments
Instance arguments are Agda’s builtin mechanism for ad-hoc overloading (~ type classes in Haskell).
Syntax:
{{x : A}} → ...
instance ...
When using a function of type {{x : A}} → B
, Agda will automatically resolve the argument if there is a unique instance of the right type in scope.
record Print {ℓ} (A : Set ℓ) : Set ℓ where field print : A → String open Print {{...}} -- print : {{r : Print A}} → A → String instance PrintBool : Print Bool print {{PrintBool}} true = "true" print {{PrintBool}} false = "false" PrintString : Print String print {{PrintString}} x = x testPrint : String testPrint = (print true) ++ (print "a string")
“To be is to do” –Socrates
“To do is to be” –Sartre
“Do be do be do” –Sinatra.
Agda is a pure language: functions have no side effects
But a typechecker has many side effects:
Monad
is a typeclass with two fields return
and _>>=_
.
M A
~ “computations that may have some side-effects (depending on M) and return an A”
Examples: Maybe
, Reader
, Error
, State
, …
do
notationdo
is syntactic sugar for repeated binds: instead of
_ : Maybe ℤ _ = (just (-[1+ 3 ])) >>= λ x → (just (+ 5) ) >>= λ y → return (x + y)you can write:
_ : Maybe ℤ _ = do x ← just (-[1+ 3 ]) y ← just (+ 5) return (x + y)
do
There can be a pattern to the left of a ←
, alternative cases can be handled in a local where
pred : ℕ → Maybe ℕ pred n = do suc m ← just n where zero → nothing return m
do
postulate test : (m n : ℕ) → Maybe (m ≡ n) cast : (m n : ℕ) → Vec ℤ m → Maybe (Vec ℤ n) cast m n xs = do refl ← test m n return xs
Pattern matching allows typechecker to learn new facts!
See V1/TypeChecker.agda.
Exercise: extend the typechecker with rules for the new syntactic constructions you added before
For type-checking variables, we need the following side-effects:
How to ensure statically that each variable in scope has a name?
All
typeP : A → Set
and xs : List A
, All P xs
associates an element of P x
to each x ∈ xs
:
data All {A : Set} (P : A → Set) : List A → Set where [] : All P [] _∷_ : ∀ {x xs} → P x → All P xs → All P (x ∷ xs)
Env Γ = All Val Γ
TCCxt Γ = All (\_ → Name) Γ
We need to modify both Γ : Cxt
and ρ : TCCxt Γ
!
Possible solutions:
Σ Cxt TCCxt
?An indexed monad = a monad with two extra parameters for the (static) input and output states
record IMonad {I : Set} (M : I → I → Set → Set) : Set₁ where field return : ∀ {A i} → A → M i i A _>>=_ : ∀ {A B i j k} → M i j A → (A → M j k B) → M i k B
Examples:
TCDecl
monad (see V2/TypeChecker.agda).Exec
monad (see V3/Interpreter.agda).We now have a correct-by-construction typechecker + interpreter.
However, some things are still missing:
Goal. Use Haskell library for generating these automatically
Import a Haskell module:
{-# FOREIGN GHC import HaskellModule.hs #-}
Bind Haskell function to Agda name:
postulate agdaName : AgdaType {-# COMPILE GHC agdaName = haskellCode #-}
Bind Haskell datatype to Agda datatype:
data D : Set where c₁ c₂ : D {-# COMPILE GHC D = data hsData (hsCon₁ | hsCon₂) #-}
-- In file `AST.agda`: {-# FOREIGN GHC import While.Abs #-} data Type : Set where bool int : Type {-# COMPILE GHC Type = data Type ( TBool | TInt ) #-}
BNFC is a Haskell library for generating Haskell code from a grammar:
See While.cf for the grammar of WHILE.
Extend the typechecker and the BNFC grammar with the new syntactic constructions you added.
Don’t forget to update the Haskell bindings in AST.agda!
Running the test suite:
make parser
: compile the parser and run it on /test/gcd.c. - make test
: compile the whole typechecker and run it on the examples in /test