123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- ..
- ::
- module language.copatterns where
- data _≡_ {A : Set} : A → A → Set where
- refl : {a : A} → a ≡ a
- data Nat : Set where
- zero : Nat
- suc : Nat → Nat
- data Bool : Set where
- true false : Bool
- {-# BUILTIN NATURAL Nat #-}
- .. _copatterns:
- **********
- Copatterns
- **********
- Consider the following record:
- ::
- record Enumeration (A : Set) : Set where
- constructor enumeration
- field
- start : A
- forward : A → A
- backward : A → A
- This gives an interface that allows us to move along the elements of a
- data type ``A``.
- For example, we can get the “third” element of a type ``A``:
- ..
- ::
- module _ where
- ::
- open Enumeration
- 3rd : {A : Set} → Enumeration A → A
- 3rd e = forward e (forward e (forward e (start e)))
- Or we can go back 2 positions starting from a given ``a``:
- ::
- backward-2 : {A : Set} → Enumeration A → A → A
- backward-2 e a = backward (backward a)
- where
- open Enumeration e
- Now, we want to use these methods on natural numbers. For this, we need
- a record of type ``Enumeration Nat``. Without copatterns, we would
- specify all the fields in a single expression:
- ..
- ::
- module Without-Copatterns where
- ::
- open Enumeration
- enum-Nat : Enumeration Nat
- enum-Nat = record {
- start = 0
- ; forward = suc
- ; backward = pred
- }
- where
- pred : Nat → Nat
- pred zero = zero
- pred (suc x) = x
- test₁ : 3rd enum-Nat ≡ 3
- test₁ = refl
- test₂ : backward-2 enum-Nat 5 ≡ 3
- test₂ = refl
- Note that if we want to use automated case-splitting and pattern
- matching to implement one of the fields, we need to do so in a separate
- definition.
- With *copatterns*, we can define the fields of a record as separate declarations,
- in the same way that we would give different cases for a function:
- ..
- ::
- module With-Copatterns where
- ::
- open Enumeration
- enum-Nat : Enumeration Nat
- start enum-Nat = 0
- forward enum-Nat n = suc n
- backward enum-Nat zero = zero
- backward enum-Nat (suc n) = n
- The resulting behaviour is the same in both cases:
- ::
- test₁ : 3rd enum-Nat ≡ 3
- test₁ = refl
- test₂ : backward-2 enum-Nat 5 ≡ 3
- test₂ = refl
- Copatterns in function definitions
- ----------------------------------
- In fact, we do not need to start at ``0``. We can allow the user to
- specify the starting element.
- Without copatterns, we just add the extra argument to the function declaration:
- ..
- ::
- module Without-Copatterns-2 where
- ::
- open Enumeration
- enum-Nat : Nat → Enumeration Nat
- enum-Nat initial = record {
- start = initial
- ; forward = suc
- ; backward = pred
- }
- where
- pred : Nat → Nat
- pred zero = zero
- pred (suc x) = x
- test₁ : 3rd (enum-Nat 10) ≡ 13
- test₁ = refl
- With copatterns, the function argument must be repeated once for each
- field in the record:
- ..
- ::
- module With-Copatterns-2 where
- ::
- open Enumeration
- enum-Nat : Nat → Enumeration Nat
- start (enum-Nat initial) = initial
- forward (enum-Nat _) n = suc n
- backward (enum-Nat _) zero = zero
- backward (enum-Nat _) (suc n) = n
- Mixing patterns and co-patterns
- -------------------------------
- Instead of allowing an arbitrary value, we want to limit the user to
- two choices: ``0`` or ``42``.
- Without copatterns, we would need an auxiliary definition to choose which
- value to start with based on the user-provided flag:
- ..
- ::
- module Without-Copatterns-3 where
- ::
- open Enumeration
- if_then_else_ : {A : Set} → Bool → A → A → A
- if true then x else _ = x
- if false then _ else y = y
- enum-Nat : Bool → Enumeration Nat
- enum-Nat ahead = record {
- start = if ahead then 42 else 0
- ; forward = suc
- ; backward = pred
- }
- where
- pred : Nat → Nat
- pred zero = zero
- pred (suc x) = x
- With copatterns, we can do the case analysis directly by pattern matching:
- ..
- ::
- module With-Copatterns-3 where
- ::
- open Enumeration
- enum-Nat : Bool → Enumeration Nat
- start (enum-Nat true) = 42
- start (enum-Nat false) = 0
- forward (enum-Nat _) n = suc n
- backward (enum-Nat _) zero = zero
- backward (enum-Nat _) (suc n) = n
- ..
- ::
- module Tip where
- .. tip:: When using copatterns to define an element of a record type,
- the fields of the record must be in scope. In the examples above,
- we use ``open Enumeration`` to bring the fields of the record into
- scope.
- Consider the first example:
- .. code-block:: agda
- enum-Nat : Enumeration Nat
- start enum-Nat = 0
- forward enum-Nat n = suc n
- backward enum-Nat zero = zero
- backward enum-Nat (suc n) = n
- If the fields of the ``Enumeration`` record are not in scope (in
- particular, the ``start`` field), then Agda will not be able to
- figure out what the first copattern means:
- .. code-block:: agda
- Could not parse the left-hand side start enum-Nat
- Operators used in the grammar:
- None
- when scope checking the left-hand side start enum-Nat in the
- definition of enum-Nat
- The solution is to open the record before using its fields:
- ..
- ::
- module Opened where
- ::
- open Enumeration
- enum-Nat : Enumeration Nat
- start enum-Nat = 0
- forward enum-Nat n = suc n
- backward enum-Nat zero = zero
- backward enum-Nat (suc n) = n
|