module Lecture1 where open import Agda.Primitive renaming (Set to Type) open import Agda.Builtin.Bool open import Agda.Builtin.Nat -- Some useful Agda commands (see the user manual for more) -- C-c C-l type check the buffer -- C-c C-n evaluate an expression (can use local variable if called from a hole) -- C-c C-c perform case analysis (type variable into hole) -- C-c C-, goal type and context -- C-c C-Space fill a hole with a term -- C-c C-r fill a hole with a function applied to an appropriate number of fresh holes -- \to or \-> unicode arrow: → -- Agda functions are commonly defined by pattern matching. not : Bool → Bool not false = true not true = false -- Infix operators can be declared by using underscores the name. -- Types and constructors can also be operators. _&&_ : Bool → Bool → Bool false && y = false true && y = y infixr 3 _&&_ -- Operators can be given precedences like in Haskell. -- Declaring a 'variable' tells Agda that you want to implicitly bind it, -- e.g. use it as a type variable. -- This is similar to how in Haskell -- -- id :: a -> a -- -- really means: -- -- id :: forall a. a -> a -- -- In Haskell any lower case identifier is a type variable, but in Agda you have to declare them -- before using them. variable A : Type -- Operators are not limited to infix operators, underscore can go whereever you want. -- There are also no restrictions on what identifier characters can be used in name, -- any non-whitespace unicode characters are fine -- (with some restrictions for reserved characters). -- This means that spaces are important in Agda: 1+2 is a valid identifier, yet 1 + 2 computes to 3. if_then_else_ : Bool → A → A → A -- "if" is polymorphic in the return type if false then x else y = y if true then x else y = x -- Exercise: Implement some more functions on booleans, -- for instance, or (_||_) and equivalence/equality (_<=>_). -- Exercise: Implement the factorial function by pattern on the natural number argument. -- A simple expression language ------------------------------- module SimpleTypes where -- Agda is declare-before-use (in contrast to Haskell). Mutual recursion can be expression -- using a `mutual` block. mutual data Expr : Type where lit : Nat → Expr add : Expr → Expr → Expr if : Cond → Expr → Expr → Expr data Cond : Type where lt : Expr → Expr → Cond and : Cond → Cond → Cond neg : Cond → Cond -- You can also express mutual recursion by declaring things before you define them. eval : Expr → Nat cond : Cond → Bool eval (lit n) = n eval (add a b) = eval a + eval b eval (if c a b) = if cond c then eval a else eval b cond (lt a b) = eval a < eval b cond (and a b) = cond a && cond b cond (neg a) = not (cond a) ex : Expr ex = add (lit 4) (add (lit 1) (lit 2)) -- Indexed types ---------------- -- Having separate data types for expressions and conditional is very -- rigid and doesn't scale well. For instance, if we wanted "if" -- expressions to be usable in conditionals we'd have to duplicate the "if" -- constructor and its handling in the eval functions. A better -- way is to have a single *indexed* data type, where the index tells us -- whether the expression is a natural number expression or a conditional. -- First we define a data type for our object-level types -- (numbers and booleans). data Ty : Type where nat : Ty bool : Ty variable t : Ty -- Then we define an expression data type indexed by an object-level type. -- Now each constructor can target a different object-level type, and the if -- constructor can be polymorphic in the type. data Expr : Ty → Type where lt : Expr nat → Expr nat → Expr bool and : Expr bool → Expr bool → Expr bool neg : Expr bool → Expr bool lit : Nat → Expr nat add : Expr nat → Expr nat → Expr nat if : Expr bool → Expr t → Expr t → Expr t -- Mapping object-level types to Agda types. -- (Note that "Value" is a type-valued function.) Value : Ty → Type Value nat = Nat Value bool = Bool -- eval now takes an expression of object type t and computes a value -- of the corresponding Agda type. eval : Expr t → Value t eval (lt e e₁) = eval e < eval e₁ eval (and e e₁) = eval e && eval e₁ eval (neg e) = not (eval e) eval (lit n) = n eval (add e e₁) = eval e + eval e₁ eval (if e e₁ e₂) = if eval e then eval e₁ else eval e₂ ex : Expr nat ex = if (lt (lit 0) (lit 1)) (add (lit 1) (lit 2)) (lit 0) -- Exercise: Add multiplication to the language -- Exercise: Define `eq : Expr nat → Expr nat → Expr bool and `or : Expr bool → Expr bool → Expr bool` -- using the existing language structures (i.e. without changing the datatypes) -- Exercise: Write down some example expressions and evaluate them with `eval` using -- C-c C-n.