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.