123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- ..
- ::
- {-# OPTIONS --rewriting --sized-types #-}
- module language.let-and-where where
- open import language.built-ins
- .. _let-and-where:
- ********************************
- Local Definitions: let and where
- ********************************
- There are two ways of declaring local definitions in Agda:
- - let-expressions
- - where-blocks
- .. _let-expressions:
- let-expressions
- ===============
- A let-expression defines an abbreviation.
- In other words, the expression that we define in a let-expression can
- neither be recursive, nor can let bound functions be defined by
- pattern matching.
- Example::
- f : Nat
- f = let h : Nat → Nat
- h m = suc (suc m)
- in h zero + h (suc zero)
- let-expressions have the general form
- .. code-block:: agda
- let f₁ : A₁₁ → … → A₁ₙ → A₁
- f₁ x₁ … xₙ = e₁
- …
- fₘ : Aₘ₁ → … → Aₘₖ → Aₘ
- fₘ x₁ … xₖ = eₘ
- in e’
- where previous definitions are in scope in later definitions. The
- type signatures can be left out if Agda can infer them.
- After type-checking, the meaning of this is simply the substitution
- ``e’[f₁ := λ x₁ … xₙ → e; …; fₘ := λ x₁ … xₖ → eₘ]``. Since Agda
- substitutes away let-bindings, they do not show up in terms Agda
- prints, nor in the goal display in interactive mode.
- .. _let-record-pattern:
- Let binding record patterns
- ---------------------------
- For a record
- .. code-block:: agda
- record R : Set where
- constructor c
- field
- f : X
- g : Y
- h : Z
- a let expression of the form
- .. code-block:: agda
- let (c x y z) = t
- in u
- will be translated internally to as
- .. code-block:: agda
- let x = f t
- y = g t
- z = h t
- in u
- This is not allowed if ``R`` is declared ``coinductive``.
- .. _where-blocks:
- where-blocks
- ============
- where-blocks are much more powerful than let-expressions, as they
- support arbitrary local definitions.
- A ``where`` can be attached to any function clause.
- where-blocks have the general form
- .. code-block:: agda
- clause
- where
- decls
- or
- .. code-block:: agda
- clause
- module M where
- decls
- A simple instance is
- .. code-block:: agda
- g ps = e
- where
- f : A₁ → … → Aₙ → A
- f p₁₁ … p₁ₙ= e₁
- …
- …
- f pₘ₁ … pₘₙ= eₘ
- Here, the ``pᵢⱼ`` are patterns of the corresponding types and ``eᵢ`` is an expression that can contain occurrences of ``f``.
- Functions defined with a where-expression must follow the rules for general definitions by pattern matching.
- Example::
- reverse : {A : Set} → List A → List A
- reverse {A} xs = rev-append xs []
- where
- rev-append : List A → List A → List A
- rev-append [] ys = ys
- rev-append (x ∷ xs) ys = rev-append xs (x ∷ ys)
- Variable scope
- --------------
- The pattern variables of the parent clause of the where-block are in
- scope; in the previous example, these are ``A`` and ``xs``. The
- variables bound by the type signature of the parent clause are not in
- scope. This is why we added the hidden binder ``{A}``.
- Scope of the local declarations
- -------------------------------
- The ``where``-definitions are not visible outside of the clause that
- owns these definitions (the parent clause). If the ``where``-block is
- given a name (form ``module M where``), then the definitions are
- available as qualified by ``M``, since module ``M`` is visible even
- outside of the parent clause. The special form of an anonymous module
- (``module _ where``) makes the definitions visible outside of the
- parent clause without qualification.
- If the parent function of a named ``where``-block
- (form ``module M where``) is ``private``,
- then module ``M`` is also ``private``.
- However, the declarations inside ``M`` are not private unless declared
- so explicitly. Thus, the following example scope checks fine::
- module Parent₁ where
- private
- parent = local
- module Private where
- local = Set
- module Public = Private
- test₁ = Parent₁.Public.local
- Likewise, a ``private`` declaration for a parent function does not
- affect the privacy of local functions defined under a
- ``module _ where``-block::
- module Parent₂ where
- private
- parent = local
- module _ where
- local = Set
- test₂ = Parent₂.local
- They can be declared ``private`` explicitly, though::
- module Parent₃ where
- parent = local
- module _ where
- private
- local = Set
- Now, ``Parent₃.local`` is not in scope.
- A ``private`` declaration for the parent of an ordinary
- ``where``-block has no effect on the local definitions, of course.
- They are not even in scope.
- Proving properties
- ==================
- Sometimes one needs to refer to local definitions in proofs about the
- parent function. In this case, the ``module ⋯ where`` variant is preferable.
- .. code-block:: agda
- reverse : {A : Set} → List A → List A
- reverse {A} xs = rev-append xs []
- module Rev where
- rev-append : List A → List A → List A
- rev-append [] ys = ys
- rev-append (x :: xs) ys = rev-append xs (x :: ys)
- This gives us access to the local function as
- .. code-block:: agda
- Rev.rev-append : {A : Set} (xs : List A) → List A → List A → List A
- Alternatively, we can define local
- functions as private to the module we are working in; hence, they
- will not be visible in any module that imports this module but it will
- allow us to prove some properties about them.
- ::
- private
- rev-append : {A : Set} → List A → List A → List A
- rev-append [] ys = ys
- rev-append (x ∷ xs) ys = rev-append xs (x ∷ ys)
- reverse' : {A : Set} → List A → List A
- reverse' xs = rev-append xs []
- More Examples (for Beginners)
- =============================
- Using a let-expression::
- tw-map : {A : Set} → List A → List (List A)
- tw-map {A} xs = let twice : List A → List A
- twice xs = xs ++ xs
- in map (\ x → twice [ x ]) xs
- Same definition but with less type information::
- tw-map' : {A : Set} → List A → List (List A)
- tw-map' {A} xs = let twice : _
- twice xs = xs ++ xs
- in map (\ x → twice [ x ]) xs
- Same definition but with a where-expression
- ::
- tw-map'' : {A : Set} → List A → List (List A)
- tw-map'' {A} xs = map (\ x → twice [ x ]) xs
- where twice : List A → List A
- twice xs = xs ++ xs
- Even less type information using let::
- g : Nat → List Nat
- g zero = [ zero ]
- g (suc n) = let sing = [ suc n ]
- in sing ++ g n
- Same definition using where::
- g' : Nat → List Nat
- g' zero = [ zero ]
- g' (suc n) = sing ++ g' n
- where sing = [ suc n ]
- More than one definition in a let::
- h : Nat → Nat
- h n = let add2 : Nat
- add2 = suc (suc n)
- twice : Nat → Nat
- twice m = m * m
- in twice add2
- More than one definition in a where::
- fibfact : Nat → Nat
- fibfact n = fib n + fact n
- where fib : Nat → Nat
- fib zero = suc zero
- fib (suc zero) = suc zero
- fib (suc (suc n)) = fib (suc n) + fib n
- fact : Nat → Nat
- fact zero = suc zero
- fact (suc n) = suc n * fact n
- Combining let and where::
- k : Nat → Nat
- k n = let aux : Nat → Nat
- aux m = pred (h m) + fibfact m
- in aux (pred n)
- where pred : Nat → Nat
- pred zero = zero
- pred (suc m) = m
|