1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027 |
- ..
- ::
- {-# OPTIONS --allow-unsolved-metas --irrelevant-projections --guardedness #-}
- module language.with-abstraction where
- open import Agda.Builtin.Nat using (Nat; zero; suc; _<_)
- open import Agda.Builtin.Bool using (Bool; true; false)
- data Comparison : Set where
- equal greater less : Comparison
- data List (A : Set) : Set where
- [] : List A
- _∷_ : A → List A → List A
- open import Agda.Builtin.Equality using (_≡_; refl)
- data ⊥ : Set where
- .. _with-abstraction:
- ****************
- With-Abstraction
- ****************
- .. contents::
- :depth: 2
- :local:
- With-abstraction was first introduced by Conor McBride [McBride2004]_ and lets
- you pattern match on the result of an intermediate computation by effectively
- adding an extra argument to the left-hand side of your function.
- Usage
- -----
- In the simplest case the ``with`` construct can be used just to discriminate on
- the result of an intermediate computation. For instance
- ..
- ::
- module verbose-usage where
- ::
- filter : {A : Set} → (A → Bool) → List A → List A
- filter p [] = []
- filter p (x ∷ xs) with p x
- filter p (x ∷ xs) | true = x ∷ filter p xs
- filter p (x ∷ xs) | false = filter p xs
- The clause containing the with-abstraction has no right-hand side. Instead it
- is followed by a number of clauses with an extra argument on the left,
- separated from the original arguments by a vertical bar (``|``).
- When the original arguments are the same in the new clauses you can use the
- ``...`` syntax:
- ..
- ::
- module ellipsis-usage where
- ::
- filter : {A : Set} → (A → Bool) → List A → List A
- filter p [] = []
- filter p (x ∷ xs) with p x
- ... | true = x ∷ filter p xs
- ... | false = filter p xs
- In this case ``...`` expands to ``filter p (x ∷ xs)``. There are three cases
- where you have to spell out the left-hand side:
- - If you want to do further pattern matching on the original
- arguments.
- - When the pattern matching on the intermediate result refines some of
- the other arguments (see :ref:`dot-patterns`).
- - To disambiguate the clauses of nested with-abstractions (see
- :ref:`nested-with-abstractions` below).
- ..
- ::
- module generalisation where
- .. _generalisation:
- Generalisation
- ~~~~~~~~~~~~~~
- The power of with-abstraction comes from the fact that the goal type
- and the type of the original arguments are generalised over the value
- of the scrutinee. See :ref:`technical-details` below for the details.
- This generalisation is important when you have to prove properties
- about functions defined using ``with``. For instance, suppose we want
- to prove that the ``filter`` function above satisfies some property
- ``P``. Starting out by pattern matching of the list we get the
- following (with the goal types shown in the holes)
- ..
- ::
- open ellipsis-usage
- ::
- postulate P : ∀ {A} → List A → Set
- postulate p-nil : ∀ {A} → P {A} []
- postulate Q : Set
- postulate q-nil : Q
- ..
- ::
- module verbose-proof where
- ::
- proof : {A : Set} (p : A → Bool) (xs : List A) → P (filter p xs)
- proof p [] = {! P [] !}
- proof p (x ∷ xs) = {! P (filter p (x ∷ xs) | p x) !}
- ..
- ::
- module ellipsis-proof where
- In the cons case we have to prove that ``P`` holds for ``filter p (x ∷ xs) | p x``.
- This is the syntax for a stuck with-abstraction---\ ``filter`` cannot reduce
- since we don't know the value of ``p x``. This syntax is used for printing, but
- is not accepted as valid Agda code. Now if we with-abstract over ``p x``, but
- don't pattern match on the result we get::
- proof : {A : Set} (p : A → Bool) (xs : List A) → P (filter p xs)
- proof p [] = p-nil
- proof p (x ∷ xs) with p x
- ... | r = {! P (filter p (x ∷ xs) | r) !}
- ..
- ::
- module ellipsis-proof-step where
- Here the ``p x`` in the goal type has been replaced by the variable ``r``
- introduced for the result of ``p x``. If we pattern match on ``r`` the
- with-clauses can reduce, giving us::
- proof : {A : Set} (p : A → Bool) (xs : List A) → P (filter p xs)
- proof p [] = p-nil
- proof p (x ∷ xs) with p x
- ... | true = {! P (x ∷ filter p xs) !}
- ... | false = {! P (filter p xs) !}
- Both the goal type and the types of the other arguments are generalised, so it
- works just as well if we have an argument whose type contains ``filter p xs``.
- ::
- proof₂ : {A : Set} (p : A → Bool) (xs : List A) → P (filter p xs) → Q
- proof₂ p [] _ = q-nil
- proof₂ p (x ∷ xs) H with p x
- ... | true = {! H : P (x ∷ filter p xs) !}
- ... | false = {! H : P (filter p xs) !}
- The generalisation is not limited to scrutinees in other with-abstractions. All
- occurrences of the term in the goal type and argument types will be
- generalised.
- Note that this generalisation is not always type correct and may
- result in a (sometimes cryptic) type error. See
- :ref:`ill-typed-with-abstractions` below for more details.
- .. _nested-with-abstractions:
- Nested with-abstractions
- ~~~~~~~~~~~~~~~~~~~~~~~~
- ..
- ::
- module compare-verbose where
- With-abstractions can be nested arbitrarily. The only thing to keep in mind in
- this case is that the ``...`` syntax applies to the closest with-abstraction.
- For example, suppose you want to use ``...`` in the definition below.
- ::
- compare : Nat → Nat → Comparison
- compare x y with x < y
- compare x y | false with y < x
- compare x y | false | false = equal
- compare x y | false | true = greater
- compare x y | true = less
- You might be tempted to replace ``compare x y`` with ``...`` in all the
- with-clauses as follows.
- .. code-block:: agda
- compare : Nat → Nat → Comparison
- compare x y with x < y
- ... | false with y < x
- ... | false = equal
- ... | true = greater
- ... | true = less -- WRONG
- This, however, would be wrong. In the last clause the ``...`` is interpreted as
- belonging to the inner with-abstraction (the whitespace is not taken into
- account) and thus expands to ``compare x y | false | true``. In this case you
- have to spell out the left-hand side and write
- ..
- ::
- module compare-ellipsis where
- ::
- compare : Nat → Nat → Comparison
- compare x y with x < y
- ... | false with y < x
- ... | false = equal
- ... | true = greater
- compare x y | true = less
- ..
- ::
- module simultaneous-abstraction where
- open import Agda.Builtin.Nat using (_+_)
- .. _simultaneous-abstraction:
- Simultaneous abstraction
- ~~~~~~~~~~~~~~~~~~~~~~~~
- You can abstract over multiple terms in a single with-abstraction. To do this
- you separate the terms with vertical bars (``|``).
- ::
- compare : Nat → Nat → Comparison
- compare x y with x < y | y < x
- ... | true | _ = less
- ... | _ | true = greater
- ... | false | false = equal
- In this example the order of abstracted terms does not matter, but in general
- it does. Specifically, the types of later terms are generalised over the values
- of earlier terms. For instance
- ::
- postulate plus-commute : (a b : Nat) → a + b ≡ b + a
- postulate P : Nat → Set
- ..
- ::
- module simultaneous-thm-unmatched where
- ::
- thm : (a b : Nat) → P (a + b) → P (b + a)
- thm a b t with a + b | plus-commute a b
- thm a b t | ab | eq = {! t : P ab, eq : ab ≡ b + a !}
- Note that both the type of ``t`` and the type of the result ``eq`` of
- ``plus-commute a b`` have been generalised over ``a + b``. If the terms in the
- with-abstraction were flipped around, this would not be the case. If we now
- pattern match on ``eq`` we get
- ..
- ::
- module simultaneous-thm-refl where
- ::
- thm : (a b : Nat) → P (a + b) → P (b + a)
- thm a b t with a + b | plus-commute a b
- thm a b t | .(b + a) | refl = {! t : P (b + a) !}
- and can thus fill the hole with ``t``. In effect we used the
- commutativity proof to rewrite ``a + b`` to ``b + a`` in the type of
- ``t``. This is such a useful thing to do that there is special syntax
- for it. See :ref:`Rewrite <with-rewrite>` below.
- ..
- ::
- module with-on-lemma where
- .. _with-on-lemma:
- A limitation of generalisation is that only occurrences of the term that are
- visible at the time of the abstraction are generalised over, but more instances
- of the term may appear once you start filling in the right-hand side or do
- further matching on the left. For instance, consider the following contrived
- example where we need to match on the value of ``f n`` for the type of ``q`` to
- reduce, but we then want to apply ``q`` to a lemma that talks about ``f n``::
- postulate
- R : Set
- P : Nat → Set
- f : Nat → Nat
- lemma : ∀ n → P (f n) → R
- Q : Nat → Set
- Q zero = ⊥
- Q (suc n) = P (suc n)
- ..
- ::
- module proof-blocked where
- ::
- proof : (n : Nat) → Q (f n) → R
- proof n q with f n
- proof n () | zero
- proof n q | suc fn = {! q : P (suc fn) !}
- ..
- ::
- module proof-lemma where
- Once we have generalised over ``f n`` we can no longer apply the lemma, which
- needs an argument of type ``P (f n)``. To solve this problem we can add the
- lemma to the with-abstraction::
- proof : (n : Nat) → Q (f n) → R
- proof n q with f n | lemma n
- proof n () | zero | _
- proof n q | suc fn | lem = lem q
- In this case the type of ``lemma n`` (``P (f n) → R``) is generalised over ``f
- n`` so in the right-hand side of the last clause we have ``q : P (suc fn)`` and
- ``lem : P (suc fn) → R``.
- See :ref:`the-inspect-idiom` below for an alternative approach.
- ..
- ::
- module with-modalities where
- .. _with-modalities:
- Making with-abstractions hidden and/or irrelevant
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- It is possible to add hiding and relevance annotations to `with`
- expressions. For example::
- module _ (A B : Set) (recompute : .B → .{{A}} → B) where
- _$_ : .(A → B) → .A → B
- f $ x with .{f} | .(f x) | .{{x}}
- ... | y = recompute y
- This can be useful for hiding with-abstractions that you do not need
- to match on but that need to be abstracted over for the result to be
- well-typed. It can also be used to abstract over the fields of a
- record type with irrelevant fields, for example::
- record EqualBools : Set₁ where
- field
- bool1 : Bool
- bool2 : Bool
- .same : bool1 ≡ bool2
- open EqualBools
- example : EqualBools → EqualBools
- example x with bool1 x | bool2 x | .(same x)
- ... | true | y′ | eq′ = record { bool1 = true; bool2 = y′; same = eq′ }
- ... | false | y′ | eq′ = record { bool1 = false; bool2 = y′; same = eq′ }
- ..
- ::
- module with-clause-underscore where
- .. _with-clause-underscore:
- Using underscores and variables in pattern repetition
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- If an ellipsis `...` cannot be used, the with-clause has to repeat (or
- refine) the patterns of the parent clause. Since Agda 2.5.3, such
- patterns can be replaced by underscores `_` if the variables they bind
- are not needed. Here is a (slightly contrived) example::
- record R : Set where
- coinductive -- disallows matching
- field f : Bool
- n : Nat
- data P (r : R) : Nat → Set where
- fTrue : R.f r ≡ true → P r zero
- nSuc : P r (suc (R.n r))
- data Q : (b : Bool) (n : Nat) → Set where
- true! : Q true zero
- suc! : ∀{b n} → Q b (suc n)
- test : (r : R) {n : Nat} (p : P r n) → Q (R.f r) n
- test r nSuc = suc!
- test r (fTrue p) with R.f r
- test _ (fTrue ()) | false
- test _ _ | true = true! -- underscore instead of (isTrue _)
- Since Agda 2.5.4, patterns can also be replaced by a variable::
- f : List Nat → List Nat
- f [] = []
- f (x ∷ xs) with f xs
- f xs0 | r = ?
- The variable `xs0` is treated as a let-bound variable with value `.x ∷
- .xs` (where `.x : Nat` and `.xs : List Nat` are out of scope). Since
- with-abstraction may change the type of variables, the instantiation
- of such let-bound variables are type checked again after
- with-abstraction.
- ..
- ::
- module with-invert {a} {A : Set a} where
- open import Agda.Builtin.Nat
- open import Agda.Builtin.Sigma
- open import Agda.Builtin.Equality
- open import Agda.Builtin.Unit
- .. _with-invert:
- Irrefutable With
- ~~~~~~~~~~~~~~~~
- When a pattern is irrefutable, we can use a pattern-matching ``with``
- instead of a traditional ``with`` block. This gives us a lightweight
- syntax to make a lot of observations before using a "proper" ``with``
- block. For a basic example of such an irrefutable pattern, see this
- unfolding lemma for ``pred`` ::
- pred : Nat → Nat
- pred zero = zero
- pred (suc n) = n
- NotNull : Nat → Set
- NotNull zero = ⊥ -- false
- NotNull (suc n) = ⊤ -- trivially true
- pred-correct : ∀ n (pr : NotNull n) → suc (pred n) ≡ n
- pred-correct n pr with suc p ← n = refl
- In the above code snippet we do not need to entertain the idea that ``n``
- could be equal to ``zero``: Agda detects that the proof ``pr`` allows us
- to dismiss such a case entirely.
- The patterns used in such an inversion clause can be arbitrary. We can
- for instance have deep patterns, e.g. projecting out the second element
- of a vector whose length is neither 0 nor 1:
- ::
- infixr 5 _∷_
- data Vec {a} (A : Set a) : Nat → Set a where
- [] : Vec A zero
- _∷_ : ∀ {n} → A → Vec A n → Vec A (suc n)
- second : ∀ {n} {pr : NotNull (pred n)} → Vec A n → A
- second vs with (_ ∷ v ∷ _) ← vs = v
- Remember the example of :ref:`simultaneous
- abstraction <simultaneous-abstraction>` from above. A simultaneous
- rewrite / pattern-matching ``with`` is to be understood as being nested.
- That is to say that the type refinements introduced by the first
- case analysis may be necessary to type the following ones.
- In the following example, in ``focusAt`` we are only able to perform
- the ``splitAt`` we are interested in because we have massaged the type
- of the vector argument using ``suc-+`` first.
- ::
- suc-+ : ∀ m n → suc m + n ≡ m + suc n
- suc-+ zero n = refl
- suc-+ (suc m) n rewrite suc-+ m n = refl
- infixr 1 _×_
- _×_ : ∀ {a b} (A : Set a) (B : Set b) → Set ?
- A × B = Σ A (λ _ → B)
- splitAt : ∀ m {n} → Vec A (m + n) → Vec A m × Vec A n
- splitAt zero xs = ([] , xs)
- splitAt (suc m) (x ∷ xs) with (ys , zs) ← splitAt m xs = (x ∷ ys , zs)
- -- focusAt m (x₀ ∷ ⋯ ∷ xₘ₋₁ ∷ xₘ ∷ xₘ₊₁ ∷ ⋯ ∷ xₘ₊ₙ)
- -- returns ((x₀ ∷ ⋯ ∷ xₘ₋₁) , xₘ , (xₘ₊₁ ∷ ⋯ ∷ xₘ₊ₙ))
- focusAt : ∀ m {n} → Vec A (suc (m + n)) → Vec A m × A × Vec A n
- focusAt m {n} vs rewrite suc-+ m n
- with (before , focus ∷ after) ← splitAt m vs
- = (before , focus , after)
- You can alternate arbitrarily many ``rewrite`` and pattern-matching
- ``with`` clauses and still perform a ``with`` abstraction afterwards
- if necessary.
- ..
- ::
- module with-rewrite where
- open import Agda.Builtin.Nat using (_+_)
- .. _with-rewrite:
- Rewrite
- ~~~~~~~
- Remember example of :ref:`simultaneous
- abstraction <simultaneous-abstraction>` from above.
- ..
- ::
- module remember-simultaneous-abstraction where
- postulate P : Nat → Set
- ::
- postulate plus-commute : (a b : Nat) → a + b ≡ b + a
- thm : (a b : Nat) → P (a + b) → P (b + a)
- thm a b t with a + b | plus-commute a b
- thm a b t | .(b + a) | refl = t
- ..
- ::
- open simultaneous-abstraction
- This pattern of rewriting by an equation by with-abstracting over it and its
- left-hand side is common enough that there is special syntax for it::
- thm : (a b : Nat) → P (a + b) → P (b + a)
- thm a b t rewrite plus-commute a b = t
- The ``rewrite`` construction takes a term ``eq`` of type ``lhs ≡ rhs``, where ``_≡_``
- is the :ref:`built-in equality type <built-in-equality>`, and expands to a
- with-abstraction of ``lhs`` and ``eq`` followed by a match of the result of
- ``eq`` against ``refl``:
- .. code-block:: agda
- f ps rewrite eq = v
- -->
- f ps with lhs | eq
- ... | .rhs | refl = v
- One limitation of the ``rewrite`` construction is that you cannot do further
- pattern matching on the arguments *after* the rewrite, since everything happens
- in a single clause. You can however do with-abstractions after the rewrite. For
- instance,
- ::
- postulate T : Nat → Set
- isEven : Nat → Bool
- isEven zero = true
- isEven (suc zero) = false
- isEven (suc (suc n)) = isEven n
- thm₁ : (a b : Nat) → T (a + b) → T (b + a)
- thm₁ a b t rewrite plus-commute a b with isEven a
- thm₁ a b t | true = t
- thm₁ a b t | false = t
- Note that the with-abstracted arguments introduced by the rewrite (``lhs`` and
- ``eq``) are not visible in the code.
- ..
- ::
- module inspect-idiom where
- .. _the-inspect-idiom:
- With-abstraction equality
- ~~~~~~~~~~~~~~~~~~~~~~~~~
- When you with-abstract a term ``t`` you lose the connection between
- ``t`` and the new argument representing its value. That's fine as long
- as all instances of ``t`` that you care about get generalised by the
- abstraction, but as we saw :ref:`above <with-on-lemma>` this is not
- always the case. In that example we used simultaneous abstraction to
- make sure that we did capture all the instances we needed.
- An alternative to that is to get Agda to remember in an equality proof
- that the patterns in the with clauses come from the expression you abstracted
- over. This is possible using the ``in`` keyword.
- ..
- ::
- open import Agda.Builtin.Sigma using (Σ; _,_)
- open import Agda.Builtin.Nat using (_+_)
- In the following artificial example, we try to prove that there exists two
- numbers such that one equals the double of the other. We start by computing
- the double of our input ``m`` and call it ``n``. We can then return the nested
- pair containing ``m``, ``n``, and we now need a proof that ``m + m ≡ n``.
- Luckily we used ``in eq`` when computing ``n`` as ``m + m`` and this ``eq``
- is exactly the proof we need.
- ::
- double : Nat → Σ Nat (λ m → Σ Nat (λ n → m + m ≡ n))
- double m with n ← m + m in eq = m , n , eq
- For a more natural example, we prove that ``filter`` (defined at the top of this
- page) is idempotent. That is to say that applying it twice to an input list is
- the same as only applying it once.
- In the ``filter-filter p (x ∷ xs)`` case, abstracting over and then matching
- on the result of ``p x`` allows the first call to ``filter p (x ∷ xs)`` to
- reduce.
- In case the element ``x`` is kept (i.e. ``p x`` is ``true``), the second call
- to ``filter`` on the LHS goes on to performs the same ``p x`` test. Because we
- have retained the proof that ``p x ≡ true`` in ``eq``, we are able to rewrite
- by this equality and get it to reduce too.
- This leads to just enough computation that we can finish the proof with
- an appeal to congruence and the induction hypothesis.
- ..
- ::
- open ellipsis-usage
- cong : {A B : Set} (f : A → B) → ∀ {x y} → x ≡ y → f x ≡ f y
- cong f refl = refl
- ::
- filter-filter : ∀ {A} p (xs : List A) → filter p (filter p xs) ≡ filter p xs
- filter-filter p [] = refl
- filter-filter p (x ∷ xs) with p x in eq
- ... | false = filter-filter p xs -- easy
- ... | true -- second filter stuck on `p x`: rewrite by `eq`!
- rewrite eq = cong (x ∷_) (filter-filter p xs)
- Alternatives to with-abstraction
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Although with-abstraction is very powerful there are cases where you cannot or
- don't want to use it. For instance, you cannot use with-abstraction if you are
- inside an expression in a right-hand side. In that case there are a couple of
- alternatives.
- Pattern lambdas
- +++++++++++++++
- Agda does not have a primitive ``case`` construct, but one can be emulated
- using :ref:`pattern matching lambdas <pattern-lambda>`. First you define a
- function ``case_of_`` as follows::
- case_of_ : ∀ {a b} {A : Set a} {B : Set b} → A → (A → B) → B
- case x of f = f x
- You can then use this function with a pattern matching lambda as the second
- argument to get a Haskell-style case expression::
- filter : {A : Set} → (A → Bool) → List A → List A
- filter p [] = []
- filter p (x ∷ xs) =
- case p x of
- λ { true → x ∷ filter p xs
- ; false → filter p xs
- }
- This version of ``case_of_`` only works for non-dependent functions. For
- dependent functions the target type will in most cases not be inferrable, but
- you can use a variant with an explicit ``B`` for this case::
- case_return_of_ : ∀ {a b} {A : Set a} (x : A) (B : A → Set b) → (∀ x → B x) → B x
- case x return B of f = f x
- The dependent version will let you generalise over the scrutinee, just like a
- with-abstraction, but you have to do it manually. Two things that it will not let you do is
- - further pattern matching on arguments on the left-hand side, and
- - refine arguments on the left by the patterns in the case expression. For
- instance if you matched on a ``Vec A n`` the ``n`` would be refined by the
- nil and cons patterns.
- Helper functions
- ++++++++++++++++
- Internally with-abstractions are translated to auxiliary functions
- (see :ref:`technical-details` below) and you can always write these
- functions manually. The downside is that the type signature for the
- helper function needs to be written out explicitly, but fortunately
- the :ref:`emacs-mode` has a command (``C-c C-h``) to generate it using
- the same algorithm that generates the type of a with-function.
- Termination checking
- ~~~~~~~~~~~~~~~~~~~~
- ..
- ::
- module Termination where
- postulate
- some-stuff : Nat
- module _ where
- The termination checker runs on the translated auxiliary functions, which
- means that some code that looks like it should pass termination checking
- does not. Specifically this happens in call chains like ``c₁ (c₂ x) ⟶ c₁ x``
- where the recursive call is under a with-abstraction. The reason is that
- the auxiliary function only gets passed ``x``, so the call chain is actually
- ``c₁ (c₂ x) ⟶ x ⟶ c₁ x``, and the termination checker cannot see that this
- is terminating. For example::
- data D : Set where
- [_] : Nat → D
- ..
- ::
- module M₁ where
- {-# TERMINATING #-}
- ::
- fails : D → Nat
- fails [ zero ] = zero
- fails [ suc n ] with some-stuff
- ... | _ = fails [ n ]
- The easiest way to work around this problem is to perform a with-abstraction
- on the recursive call up front::
- fixed : D → Nat
- fixed [ zero ] = zero
- fixed [ suc n ] with fixed [ n ] | some-stuff
- ... | rec | _ = rec
- ..
- ::
- module M₂ where
- {-# TERMINATING #-}
- If the function takes more arguments you might need to abstract over a
- partial application to just the structurally recursive argument. For instance,
- ::
- fails : Nat → D → Nat
- fails _ [ zero ] = zero
- fails _ [ suc n ] with some-stuff
- ... | m = fails m [ n ]
- fixed : Nat → D → Nat
- fixed _ [ zero ] = zero
- fixed _ [ suc n ] with (λ m → fixed m [ n ]) | some-stuff
- ... | rec | m = rec m
- ..
- ::
- postulate
- A possible complication is that later with-abstractions might change the
- type of the abstracted recursive call::
- T : D → Set
- suc-T : ∀ {n} → T [ n ] → T [ suc n ]
- zero-T : T [ zero ]
- ..
- ::
- module M₃ where
- {-# TERMINATING #-}
- ::
- fails : (d : D) → T d
- fails [ zero ] = zero-T
- fails [ suc n ] with some-stuff
- ... | _ with [ n ]
- ... | z = suc-T (fails [ n ])
- Trying to abstract over the recursive call as before does not work in this case.
- .. code-block:: agda
- still-fails : (d : D) → T d
- still-fails [ zero ] = zero-T
- still-fails [ suc n ] with still-fails [ n ] | some-stuff
- ... | rec | _ with [ n ]
- ... | z = suc-T rec -- Type error because rec : T z
- To solve the problem you can add ``rec`` to the with-abstraction messing up
- its type. This will prevent it from having its type changed::
- fixed : (d : D) → T d
- fixed [ zero ] = zero-T
- fixed [ suc n ] with fixed [ n ] | some-stuff
- ... | rec | _ with rec | [ n ]
- ... | _ | z = suc-T rec
- Performance considerations
- ~~~~~~~~~~~~~~~~~~~~~~~~~~
- The :ref:`generalisation step <generalisation>` of a with-abstraction
- needs to normalise the scrutinee and the goal and argument types to
- make sure that all instances of the scrutinee are generalised. The
- generalisation also needs to be type checked to make sure that it's
- not :ref:`ill-typed <ill-typed-with-abstractions>`. This makes it
- expensive to type check a with-abstraction if
- - the normalisation is expensive,
- - the normalised form of the goal and argument types are big, making finding
- the instances of the scrutinee expensive,
- - type checking the generalisation is expensive, because the types are big, or
- because checking them involves heavy computation.
- In these cases it is worth looking at the `alternatives to with-abstraction`_
- from above.
- .. _technical-details:
- Technical details
- -----------------
- Internally with-abstractions are translated to auxiliary functions---there are
- no with-abstractions in the :ref:`core-language`. This translation proceeds as
- follows. Given a with-abstraction
- .. math::
- :nowrap:
- \[\arraycolsep=1.4pt
- \begin{array}{lrllcll}
- \multicolumn{3}{l}{f : \Gamma \to B} \\
- f ~ ps & \mathbf{with} ~ & t_1 & | & \ldots & | ~ t_m \\
- f ~ ps_1 & | ~ & q_{11} & | & \ldots & | ~ q_{1m} &= v_1 \\
- \vdots \\
- f ~ ps_n & | ~ & q_{n1} & | & \ldots & | ~ q_{nm} &= v_n
- \end{array}\]
- where :math:`\Delta \vdash ps : \Gamma` (i.e. :math:`\Delta` types the
- variables bound in :math:`ps`), we
- - Infer the types of the scrutinees :math:`t_1 : A_1, \ldots, t_m : A_m`.
- - Partition the context :math:`\Delta` into :math:`\Delta_1` and
- :math:`\Delta_2` such that :math:`\Delta_1` is the smallest context where
- :math:`\Delta_1 \vdash t_i : A_i` for all :math:`i`, i.e., where the scrutinees are well-typed.
- Note that the partitioning is not required to be a split,
- :math:`\Delta_1\Delta_2` can be a (well-formed) reordering of :math:`\Delta`.
- - Generalise over the :math:`t_i` s, by computing
- .. math::
- C = (w_1 : A_1)(w_1 : A_2')\ldots(w_m : A_m') \to \Delta_2' \to B'
- such that the normal form of :math:`C` does not contain any :math:`t_i` and
- .. math::
- A_i'[w_1 := t_1 \ldots w_{i - 1} := t_{i - 1}] \simeq A_i
- (\Delta_2' \to B')[w_1 := t_1 \ldots w_m := t_m] \simeq \Delta_2 \to B
- where :math:`X \simeq Y` is equality of the normal forms of :math:`X` and
- :math:`Y`. The type of the auxiliary function is then :math:`\Delta_1 \to C`.
- - Check that :math:`\Delta_1 \to C` is type correct, which is not
- guaranteed (see :ref:`below <ill-typed-with-abstractions>`).
- - Add a function :math:`f_{aux}`, mutually recursive with :math:`f`, with the
- definition
- .. math::
- :nowrap:
- \[\arraycolsep=1.4pt
- \begin{array}{llll}
- \multicolumn{4}{l}{f_{aux} : \Delta_1 \to C} \\
- f_{aux} ~ ps_{11} & \mathit{qs}_1 & ps_{21} &= v_1 \\
- \vdots \\
- f_{aux} ~ ps_{1n} & \mathit{qs}_n & ps_{2n} &= v_n \\
- \end{array}\]
- where :math:`\mathit{qs}_i = q_{i1} \ldots q_{im}`, and :math:`ps_{1i} : \Delta_1` and
- :math:`ps_{2i} : \Delta_2` are the patterns from :math:`ps_i` corresponding to
- the variables of :math:`ps`. Note that due to the possible reordering of the
- partitioning of :math:`\Delta` into :math:`\Delta_1` and :math:`\Delta_2`,
- the patterns :math:`ps_{1i}` and :math:`ps_{2i}` can be in a different order
- from how they appear :math:`ps_i`.
- - Replace the with-abstraction by a call to :math:`f_{aux}` resulting in the
- final definition
- .. math::
- :nowrap:
- \[\arraycolsep=1.4pt
- \begin{array}{l}
- f : \Gamma \to B \\
- f ~ ps = f_{aux} ~ \mathit{xs}_1 ~ ts ~ \mathit{xs}_2
- \end{array}\]
- where :math:`ts = t_1 \ldots t_m` and :math:`\mathit{xs}_1` and
- :math:`\mathit{xs}_2` are the variables from :math:`\Delta` corresponding to
- :math:`\Delta_1` and :math:`\Delta_2` respectively.
- ..
- ::
- module examples where
- Examples
- ~~~~~~~~
- Below are some examples of with-abstractions and their translations.
- ::
- postulate
- A : Set
- _+_ : A → A → A
- T : A → Set
- mkT : ∀ x → T x
- P : ∀ x → T x → Set
- -- the type A of the with argument has no free variables, so the with
- -- argument will come first
- f₁ : (x y : A) (t : T (x + y)) → T (x + y)
- f₁ x y t with x + y
- f₁ x y t | w = {!!}
- -- Generated with function
- f-aux₁ : (w : A) (x y : A) (t : T w) → T w
- f-aux₁ w x y t = {!!}
- -- x and p are not needed to type the with argument, so the context
- -- is reordered with only y before the with argument
- f₂ : (x y : A) (p : P y (mkT y)) → P y (mkT y)
- f₂ x y p with mkT y
- f₂ x y p | w = {!!}
- f-aux₂ : (y : A) (w : T y) (x : A) (p : P y w) → P y w
- f-aux₂ y w x p = {!!}
- postulate
- H : ∀ x y → T (x + y) → Set
- -- Multiple with arguments are always inserted together, so in this case
- -- t ends up on the left since it’s needed to type h and thus x + y isn’t
- -- abstracted from the type of t
- f₃ : (x y : A) (t : T (x + y)) (h : H x y t) → T (x + y)
- f₃ x y t h with x + y | h
- f₃ x y t h | w₁ | w₂ = {! t : T (x + y), goal : T w₁ !}
- f-aux₃ : (x y : A) (t : T (x + y)) (h : H x y t) (w₁ : A) (w₂ : H x y t) → T w₁
- f-aux₃ x y t h w₁ w₂ = {!!}
- -- But earlier with arguments are abstracted from the types of later ones
- f₄ : (x y : A) (t : T (x + y)) → T (x + y)
- f₄ x y t with x + y | t
- f₄ x y t | w₁ | w₂ = {! t : T (x + y), w₂ : T w₁, goal : T w₁ !}
- f-aux₄ : (x y : A) (t : T (x + y)) (w₁ : A) (w₂ : T w₁) → T w₁
- f-aux₄ x y t w₁ w₂ = {!!}
- ..
- ::
- module ill-typed where
- .. _ill-typed-with-abstractions:
- Ill-typed with-abstractions
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
- As mentioned above, generalisation does not always produce well-typed results.
- This happens when you abstract over a term that appears in the *type* of a subterm
- of the goal or argument types. The simplest example is abstracting over the
- first component of a dependent pair. For instance,
- ::
- postulate
- A : Set
- B : A → Set
- H : (x : A) → B x → Set
- .. code-block:: agda
- bad-with : (p : Σ A B) → H (fst p) (snd p)
- bad-with p with fst p
- ... | _ = {!!}
- Here, generalising over ``fst p`` results in an ill-typed application ``H w
- (snd p)`` and you get the following type error:
- .. code-block:: none
- fst p != w of type A
- when checking that the type (p : Σ A B) (w : A) → H w (snd p) of
- the generated with function is well-formed
- This message can be a little difficult to interpret since it only prints the
- immediate problem (``fst p != w``) and the full type of the with-function. To
- get a more informative error, pointing to the location in the type where the
- error is, you can copy and paste the with-function type from the error message
- and try to type check it separately.
- .. [McBride2004] C. McBride and J. McKinna. **The view from the left**. Journal of Functional Programming, 2004.
- http://strictlypositive.org/vfl.pdf.
- .. _std-lib: https://github.com/agda/agda-stdlib
- .. _agda-prelude: https://github.com/UlfNorell/agda-prelude
|