https://github.com/alhassy/gentle-intro-to-reflection
A slow-paced introduction to reflection in Agda. ---Tactics!
https://github.com/alhassy/gentle-intro-to-reflection
agda exercise macro proof-pattern tactics tutorial
Last synced: 4 months ago
JSON representation
A slow-paced introduction to reflection in Agda. ---Tactics!
- Host: GitHub
- URL: https://github.com/alhassy/gentle-intro-to-reflection
- Owner: alhassy
- Created: 2019-05-14T15:50:56.000Z (about 7 years ago)
- Default Branch: master
- Last Pushed: 2022-05-25T01:25:28.000Z (about 4 years ago)
- Last Synced: 2025-04-24T04:38:23.099Z (about 1 year ago)
- Topics: agda, exercise, macro, proof-pattern, tactics, tutorial
- Language: Agda
- Homepage:
- Size: 57.6 KB
- Stars: 101
- Watchers: 5
- Forks: 9
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
A slow-paced introduction to reflection in Agda. โTactics!
**Abstract**
*One proof for two different theorems!*
Let's learn how we can do that in Agda.
This tutorial is the result of mostly experimenting with the
[documentation](https://agda.readthedocs.io/en/v2.5.2/language/reflection.html) on Agda's reflection mechanism, which essentially
only exposes the reflection interface and provides a few tiny examples.
The goal of this tutorial is to contain a diverse variety of examples,
along with occasional exercises for the reader.
Examples include:
- String manipulation of built-in identifier names. ๐
- Handy dandy combinators for AST formation: `๐๐๐ถ, ฮป๐_โฆ_, โฆ`. ๐
- Numerous examples of quotation of terms and types. ๐ฏ
- Wholesale derivation of singleton types for an example datatype,
along with derivable proofs ๐ ๐ต
- Automating proofs that are only `refl` *with* pattern matching ๐
- Discussion of C-style macros in Agda ๐ต
- Abstracting proofs patterns without syntactic overhead using macros ๐ช ๐ผ
- Remarks on what I could not do, possibly since it cannot be done :sob:
Everything here works with Agda version 2.6.0.
This document is a literate Agda file written using
the (poorly coded) [org-agda](https://alhassy.github.io/literate/) framework.
A pure `.agda` file can be found [here](tangled.agda).
# Table of Contents
1. [Imports](#org0aa53a5)
2. [Introduction](#org93d5c28)
3. [`NAME` โType of known identifiers](#org8c7be7f)
4. [`Arg` โType of arguments](#org479222b)
5. [`Term` โType of terms](#orgc533d22)
1. [Example: Simple Types](#org9e1b7a8)
2. [Example: Simple Terms](#org5f8d00c)
3. [A relationship between `quote` and `quoteTerm`](#org24877d0)
4. [Example: Lambda Terms](#orgb235933)
6. [Metaprogramming with The Typechecking Monad `TC`](#org24401fa)
7. [Unquoting โMaking new functions & types](#org2291c5c)
8. [Sidequest: Avoid tedious `refl` proofs](#org9553964)
9. [Macros โAbstracting Proof Patterns](#org17511af)
1. [C-style macros](#orgce93e44)
2. [Tedious Repetitive Proofs No More!](#org3df21e0)
10. [Our First Real Proof Tactic](#orgf24bc9c)
11. [Heuristic for Writing a Macro](#orge7318a1)
12. [What about somewhere deep within a subexpression?](#org00976e8)
# Imports
First, some necessary imports:
module gentle-intro-to-reflection where
import Level as Level
open import Reflection hiding (name; Type)
open import Relation.Binary.PropositionalEquality hiding ([_])
open import Relation.Unary using (Decidable)
open import Relation.Nullary
open import Data.Unit
open import Data.Nat as Nat hiding (_โ_)
open import Data.Bool
open import Data.Product
open import Data.List as List
open import Data.Char as Char
open import Data.String as String
# Introduction
*Reflection* is the ability to convert program code into an abstract syntax,
a data structure that can be manipulated like any other.
Consider, for example, the tedium of writing a decidable equality for an enumerated type.
Besides being tedious and error-prone, the inexpressibility of
what should be a mechanically-derivable concept
obscures the corresponding general principle underlying it, thus foregoing
any machine assistance in ensuring any correctness or safety-ness guarantees.
Reflection allows a more economical and disciplined approach.
It is the aim of this tutorial to show how to get started with reflection in Agda.
To the best of my knowledge there is no up to date tutorial on this matter.
There are three main types in Agda's reflection mechanism:
`Name, Arg, Term`. We will learn about them with the aid of
this following simple enumerated typed, as well as other standard types.
data RGB : Set where
Red Green Blue : RGB
# `NAME` โType of known identifiers
`Name` is the type of quoting identifiers, Agda names.
Elements of this type can be formed and pattern matched using
the `quote` keyword.
a-name : Name
a-name = quote โ
isNat : Name โ Bool
isNat (quote โ) = true
isNat _ = false
-- bad : Set โ Name
-- bad s = quote s {- s is not known -}
- `NAME` comes equipped with equality, ordering, and a show function.
- Quote will not work on function arguments; the identifier must be known.
Let's show names:
_ : showName (quote _โก_) โก "Agda.Builtin.Equality._โก_"
_ = refl
_ : showName (quote Red) โก "gentle-intro-to-reflection.RGB.Red"
_ = refl
It would be nice to have `Red` be shown as just `โRGB.Redโ`.
First, let's introduce some โprogrammingโ helpers to treat Agda strings as if they
where Haskell strings, and likewise to treat predicates as decidables.
{- Like โ$โ but for strings. -}
_โจ๐ฎโฉ_ : (List Char โ List Char) โ String โ String
f โจ๐ฎโฉ s = fromList (f (toList s))
{- This should be in the standard library; I could not locate it. -}
toDec : โ {โ} {A : Set โ} โ (p : A โ Bool) โ Decidable {โ} {A} (ฮป a โ p a โก true)
toDec p x with p x
toDec p x | false = no ฮป ()
toDec p x | true = yes refl
We can now easily obtain the module's name, then drop it from the data constructor's name.
module-name : String
module-name = takeWhile (toDec (ฮป c โ not (c Char.== '.'))) โจ๐ฎโฉ showName (quote Red)
_ : module-name โก "gentle-intro-to-reflection"
_ = refl
strName : Name โ String
strName n = drop (1 + String.length module-name) โจ๐ฎโฉ showName n
{- The โ1 +โ is for the โ.โ seperator in qualified names. -}
_ : strName (quote Red) โก "RGB.Red"
_ = refl
`NAME` essentially provides us with the internal representation of a known name,
for which we can query to obtain its definition or type.
Later we will show how to get the type constructors of `โ` from its name.
# `Arg` โType of arguments
Arguments in Agda may be hidden or computationally irrelevant.
This information is captured by the `Arg` type.
{- Arguments can be (visible), {hidden}, or โฆinstanceโฆ -}
data Visibility : Set where
visible hidden instanceโฒ : Visibility
{-Arguments can be relevant or irrelevant: -}
data Relevance : Set where
relevant irrelevant : Relevance
{- Visibility and relevance characterise the behaviour of an argument: -}
data ArgInfo : Set where
arg-info : (v : Visibility) (r : Relevance) โ ArgInfo
data Arg (A : Set) : Set where
arg : (i : ArgInfo) (x : A) โ Arg A
For example, let's create some helpers that make arguments of any given type `A`:
{- ๐isible ๐elevant ๐ถrgument -}
๐๐๐ถ : {A : Set} โ A โ Arg A
๐๐๐ถ = arg (arg-info visible relevant)
{- ๐ฝidden ๐elevant ๐ถrgument -}
๐ฝ๐๐ถ : {A : Set} โ A โ Arg A
๐ฝ๐๐ถ = arg (arg-info hidden relevant)
Below are the variable counterparts, for the `Term` datatype,
which will be discussed shortly.
- Variables are De Bruijn indexed and may be applied to a list of arguments.
- The index *n* refers to the argument that is *n* locations away from โhereโ.
{- ๐isible ๐elevant ๐ariable -}
๐๐๐ : (debruijn : โ) (args : List (Arg Term)) โ Arg Term
๐๐๐ n args = arg (arg-info visible relevant) (var n args)
{- ๐ฝidden ๐elevant ๐ariable -}
๐ฝ๐๐ : (debruijn : โ) (args : List (Arg Term)) โ Arg Term
๐ฝ๐๐ n args = arg (arg-info hidden relevant) (var n args)
# `Term` โType of terms
We use the `quoteTerm` keyword to turn a well-typed fragment of code
โconcrete syntaxโ into a value of the `Term` datatype โthe abstract syntax.
Here's the definition of `Term`:
data Term where
{- A variable has a De Bruijn index and may be applied to arguments. -}
var : (x : โ) (args : List (Arg Term)) โ Term
{- Constructors and definitions may be applied to a list of arguments. -}
con : (c : Name) (args : List (Arg Term)) โ Term
def : (f : Name) (args : List (Arg Term)) โ Term
{- ฮป-abstractions bind one varaible; โtโ is the string name of the variable
along with the body of the lambda.
-}
lam : (v : Visibility) (t : Abs Term) โ Term {- Abs A โ
String ร A -}
pat-lam : (cs : List Clause) (args : List (Arg Term)) โ Term
{- Telescopes, or function types; ฮป-abstraction for types. -}
pi : (a : Arg Type) (b : Abs Type) โ Term
{- โSet nโ or some term that denotes a type -}
agda-sort : (s : Sort) โ Term
{- Metavariables; introduced via quoteTerm -}
meta : (x : Meta) โ List (Arg Term) โ Term
{- Literal โ
โ | Word64 | Float | Char | String | Name | Meta -}
lit : (l : Literal) โ Term
{- Items not representable by this AST; e.g., a hole. -}
unknown : Term {- Treated as '_' when unquoting. -}
data Sort where
set : (t : Term) โ Sort {- A Set of a given (possibly neutral) level. -}
lit : (n : Nat) โ Sort {- A Set of a given concrete level. -}
unknown : Sort
data Clause where
clause : (ps : List (Arg Pattern)) (t : Term) โ Clause
absurd-clause : (ps : List (Arg Pattern)) โ Clause
## Example: Simple Types
Here are three examples of โdefโined names, the first two do not take an argument.
The last takes a visible and relevant argument, ๐๐๐ถ, that is a literal natural.
import Data.Vec as V
import Data.Fin as F
_ : quoteTerm โ โก def (quote โ) []
_ = refl
_ : quoteTerm V.Vec โก def (quote V.Vec) []
_ = refl
_ : quoteTerm (F.Fin 3) โก def (quote F.Fin) (๐๐๐ถ (lit (nat 3)) โท [])
_ = refl
## Example: Simple Terms
Elementary numeric quotations:
_ : quoteTerm 1 โก lit (nat 1)
_ = refl
_ : quoteTerm (suc zero)
โก con (quote suc) (arg (arg-info visible relevant) (quoteTerm zero) โท [])
_ = refl
{- Using our helper ๐๐๐ถ -}
_ : quoteTerm (suc zero) โก con (quote suc) (๐๐๐ถ (quoteTerm zero) โท [])
_ = refl
The first example below demonstrates that `true` is a type โconโstructor
that takes no arguments, whence the `[]`. The second example shows that
`_โก_` is a defined name, not currently applied to any arguments.
The final example has propositional equality applied to two arguments.
_ : quoteTerm true โก con (quote true) []
_ = refl
_ : quoteTerm _โก_ โก def (quote _โก_) []
_ = refl
_ : quoteTerm ("b" โก "a")
โก def (quote _โก_)
( ๐ฝ๐๐ถ (def (quote Level.zero) [])
โท ๐ฝ๐๐ถ (def (quote String) [])
โท ๐๐๐ถ (lit (string "b"))
โท ๐๐๐ถ (lit (string "a")) โท [])
_ = refl
Notice that a propositional equality actually has four arguments โa level, a type, and two argumentsโ
where the former two happen
to be inferrable from the latter.
Here is a more polymorphic example:
_ : โ {level : Level.Level}{Type : Set level} (x y : Type)
โ quoteTerm (x โก y)
โก def (quote _โก_)
(๐ฝ๐๐ 3 [] โท ๐ฝ๐๐ 2 [] โท ๐๐๐ 1 [] โท ๐๐๐ 0 [] โท [])
_ = ฮป x y โ refl
Remember that a De Bruijn index `n` refers to the lambda variable
that is `n+1` lambdas away from its use site.
For example, `๐๐๐ 1` means starting at the `โฏ โก โฏ`, go `1+1`
lambdas away to get the variable `x`.
We will demonstrate an example of a section, say
`โก_ "b"`, below when discussing lambda abstractions.
## A relationship between `quote` and `quoteTerm`
Known names `๐ป` in a quoted term are denoted by a `quote ๐ป` in the AST representation.
For example โI will use this ๐ปโด๐๐ for my postulated itemsโ
postulate ๐ โฌ : Set
postulate ๐ป : ๐ โ โฌ
_ : quoteTerm ๐ป โก def (quote ๐ป) []
_ = refl
In contrast, names that *vary* are denoted by a `var` constructor in the AST representation.
module _ {A B : Set} {f : A โ B} where
_ : quoteTerm f โก var 0 []
_ = refl
## Example: Lambda Terms
First we show how reduction with lambdas works then we show how lambda functions
are represented as `Term` values.
`quoteTerm` typechecks and normalises its argument before yielding a `Term` value.
_ : quoteTerm ((ฮป x โ x) "nice") โก lit (string "nice")
_ = refl
Eta-reduction happens, `f โ ฮป x โ f x`.
id : {A : Set} โ A โ A
id x = x
_ : quoteTerm (ฮป (x : โ) โ id x)
โก def (quote id) (๐ฝ๐๐ถ (def (quote โ) []) โท [])
_ = refl
No delta-reduction happens; function definitions are not elaborated.
_ : quoteTerm (id "a")
โก def (quote id)
(๐ฝ๐๐ถ (def (quote String) []) โท ๐๐๐ถ (lit (string "a")) โท [])
_ = refl
Here is a simple identity function on the Booleans.
A โlamโbda with a โvisibleโ โabsโtract argument named `"x"` is introduced
having as body merely being the 0 nearest-bound variable, applied to an empty
list of arguments.
_ : quoteTerm (ฮป (x : Bool) โ x) โก lam visible (abs "x" (var 0 []))
_ = refl
Here is a more complicated lambda abstraction: Note that `f a` is represented as
the variable 0 lambdas away from the body applied to the variable 1 lambda away
from the body.
_ : quoteTerm (ฮป (a : โ) (f : โ โ โ) โ f a)
โก lam visible (abs "a"
(lam visible (abs "f"
(var 0 (arg (arg-info visible relevant) (var 1 []) โท [])))))
_ = refl
This is rather messy, let's introduce some syntactic sugar to make it more readable.
infixr 5 ฮป๐_โฆ_ ฮป๐ฝ_โฆ_
ฮป๐_โฆ_ ฮป๐ฝ_โฆ_ : String โ Term โ Term
ฮป๐ x โฆ body = lam visible (abs x body)
ฮป๐ฝ x โฆ body = lam hidden (abs x body)
Now the previous example is a bit easier on the eyes:
_ : quoteTerm (ฮป (a : โ) (f : โ โ โ) โ f a)
โก ฮป๐ "a" โฆ ฮป๐ "f" โฆ var 0 [ ๐๐๐ถ (var 1 []) ]
_ = refl
Using that delicious sugar, let's look at the constant function a number of ways.
_ : {A B : Set} โ quoteTerm (ฮป (a : A) (b : B) โ a)
โก ฮป๐ "a" โฆ (ฮป๐ "b" โฆ var 1 [])
_ = refl
_ : quoteTerm (ฮป {A B : Set} (a : A) (_ : B) โ a)
โก (ฮป๐ฝ "A" โฆ (ฮป๐ฝ "B" โฆ (ฮป๐ "a" โฆ (ฮป๐ "_" โฆ var 1 []))))
_ = refl
const : {A B : Set} โ A โ B โ A
const a _ = a
_ : quoteTerm const โก def (quote const) []
_ = refl
Finally, here's an example of a section.
_ : quoteTerm (_โก "b")
โก ฮป๐ "section" โฆ
(def (quote _โก_)
(๐ฝ๐๐ถ (def (quote Level.zero) []) โท
๐ฝ๐๐ถ (def (quote String) []) โท
๐๐๐ถ (var 0 []) โท
๐๐๐ถ (lit (string "b")) โท []))
_ = refl
# Metaprogramming with The Typechecking Monad `TC`
The `TC` monad provides an interface to Agda's type checker.
postulate
TC : โ {a} โ Set a โ Set a
returnTC : โ {a} {A : Set a} โ A โ TC A
bindTC : โ {a b} {A : Set a} {B : Set b} โ TC A โ (A โ TC B) โ TC B
In order to use `do`-notation we need to have the following definitions in scope.
_>>=_ : โ {a b} {A : Set a} {B : Set b} โ TC A โ (A โ TC B) โ TC B
_>>=_ = bindTC
_>>_ : โ {a b} {A : Set a} {B : Set b} โ TC A โ TC B โ TC B
_>>_ = ฮป p q โ p >>= (ฮป _ โ q)
The primitives of `TC` can be seen on the [documentation](https://agda.readthedocs.io/en/v2.6.0/language/reflection.html#type-checking-computations) page; below are a few notable
ones that we may use. Other primitives include support for the current context,
type errors, and metavariables.
postulate
{- Take what you have and try to make it fit into the current goal. -}
unify : (have : Term) (goal : Term) โ TC โค
{- Try first computation, if it crashes with a type error, try the second. -}
catchTC : โ {a} {A : Set a} โ TC A โ TC A โ TC A
{- Infer the type of a given term. -}
inferType : Term โ TC Type
{- Check a term against a given type. This may resolve implicit arguments
in the term, so a new refined term is returned. Can be used to create
new metavariables: newMeta t = checkType unknown t -}
checkType : Term โ Type โ TC Term
{- Compute the normal form of a term. -}
normalise : Term โ TC Term
{- Quote a value, returning the corresponding Term. -}
quoteTC : โ {a} {A : Set a} โ A โ TC Term
{- Unquote a Term, returning the corresponding value. -}
unquoteTC : โ {a} {A : Set a} โ Term โ TC A
{- Create a fresh name. -}
freshName : String โ TC Name
{- Declare a new function of the given type. The function must be defined
later using 'defineFun'. Takes an Arg Name to allow declaring instances
and irrelevant functions. The Visibility of the Arg must not be hidden. -}
declareDef : Arg Name โ Type โ TC โค
{- Define a declared function. The function may have been declared using
'declareDef' or with an explicit type signature in the program. -}
defineFun : Name โ List Clause โ TC โค
{- Get the type of a defined name. Replaces 'primNameType'. -}
getType : Name โ TC Type
{- Get the definition of a defined name. Replaces 'primNameDefinition'. -}
getDefinition : Name โ TC Definition
{- Change the behaviour of inferType, checkType, quoteTC, getContext
to normalise (or not) their results. The default behaviour is no
normalisation. -}
withNormalisation : โ {a} {A : Set a} โ Bool โ TC A โ TC A
`TC` computations, or โmetaprogramsโ, can be run by declaring them as macros or by
unquoting. Let's begin with the former.
# Unquoting โMaking new functions & types
Recall our `RGB` example type was a simple enumeration consisting of `Red, Green, Blue`.
Consider the singleton type:
data IsRed : RGB โ Set where
yes : IsRed Red
The name `Red` completely determines this datatype; so let's try to generate it
mechanically. Unfortunately, as far as I could tell, there is currently no way
to unquote `data` declarations. As such, we'll settle for the following
isomorphic functional formulation:
IsRed : RGB โ Set
IsRed x = x โก Red
First, let's quote the relevant parts, for readability.
โโโโ : Arg Term
โโโโ = ๐ฝ๐๐ถ (def (quote Level.zero) [])
โRGBโ : Arg Term
โRGBโ = ๐ฝ๐๐ถ (def (quote RGB) [])
โRedโ : Arg Term
โRedโ = ๐๐๐ถ (con (quote Red) [])
The first two have a nearly identical definition and it would be nice to
mechanically derive themโฆ
Anyhow,
we use the `unquoteDecl` keyword, which allows us to obtain a `NAME` value, `IsRed`.
We then quote the desired type, declare a function of that type, then define it
using the provided `NAME`.
unquoteDecl IsRed =
do ty โ quoteTC (RGB โ Set)
declareDef (๐๐๐ถ IsRed) ty
defineFun IsRed [ clause [ ๐๐๐ถ (var "x") ] (def (quote _โก_) (โโโโ โท โRGBโ โท โRedโ โท ๐๐๐ 0 [] โท [])) ]
Let's try out our newly declared type.
red-is-a-solution : IsRed Red
red-is-a-solution = refl
green-is-not-a-solution : ยฌ (IsRed Green)
green-is-not-a-solution = ฮป ()
red-is-the-only-solution : โ {c} โ IsRed c โ c โก Red
red-is-the-only-solution refl = refl
There is a major problem with using `unquoteDef` outright like this:
We cannot step-wise refine our program using holes `?`, since that would
result in unsolved meta-variables. Instead, we split this process into two stages:
A programming stage, then an unquotation stage.
{- Definition stage, we can use โ?โ as we form this program. -}
define-Is : Name โ Name โ TC โค
define-Is is-name qcolour = defineFun is-name
[ clause [ ๐๐๐ถ (var "x") ] (def (quote _โก_) (โโโโ โท โRGBโ โท ๐๐๐ถ (con qcolour []) โท ๐๐๐ 0 [] โท [])) ]
declare-Is : Name โ Name โ TC โค
declare-Is is-name qcolour =
do let ฮท = is-name
ฯ โ quoteTC (RGB โ Set)
declareDef (๐๐๐ถ ฮท) ฯ
defineFun is-name
[ clause [ ๐๐๐ถ (var "x") ]
(def (quote _โก_) (โโโโ โท โRGBโ โท ๐๐๐ถ (con qcolour []) โท ๐๐๐ 0 [] โท [])) ]
{- Unquotation stage -}
IsRedโฒ : RGB โ Set
unquoteDef IsRedโฒ = define-Is IsRedโฒ (quote Red)
{- Trying it out -}
_ : IsRedโฒ Red
_ = refl
Notice that if we use โunquoteDefโ, we must provide a type signature.
We only do so for illustration; the next code block avoids such a redundancy by
using โunquoteDeclโ.
The above general approach lends itself nicely to the other data constructors as well:
unquoteDecl IsBlue = declare-Is IsBlue (quote Blue)
unquoteDecl IsGreen = declare-Is IsGreen (quote Green)
{- Example use -}
disjoint-rgb : โ{c} โ ยฌ (IsBlue c ร IsGreen c)
disjoint-rgb (refl , ())
The next natural step is to avoid manually invoking `declare-Is` for each constructor.
Unfortunately, it seems fresh names are not accessible, for some reason. ๐ข
For example, you would think the following would produce a function
named `gentle-intro-to-reflection.identity`. Yet, it is not in scope.
I even tried extracting the definition to its own file and no luck.
unquoteDecl {- identity -}
= do {- let ฮท = identity -}
ฮท โ freshName "identity"
ฯ โ quoteTC (โ {A : Set} โ A โ A)
declareDef (๐๐๐ถ ฮท) ฯ
defineFun ฮท [ clause [ ๐๐๐ถ (var "x") ] (var 0 []) ]
{- โidentityโ is not in scope!?
_ : โ {x : โ} โ identity x โก x
_ = refl
-}
**Exercises**:
1. Comment out the `freshName` line above and uncomment the surrounding artifacts to so that the above
unit test goes through.
2. Using that as a template, unquote a function `everywhere-0 : โ โ โ` that is constantly 0.
3. Unquote the constant combinator `K : {A B : Set} โ A โ B โ A`.
unquoteDecl everywhere-0
= do โฏ
_ : everywhere-0 3 โก 0
_ = refl
unquoteDecl K
= do โฏ
_ : K 3 "cat" โก 3
_ = refl
**Bonus:** Proofs of a singleton type such as `IsRed` are essentially the same for all singleton types
over `RGB`. Write, in two stages, a metaprogram that demonstrates each singleton type has a single member
โc.f., `red-is-the-only-solution` from above. Hint: This question is as easy as the ones before it.
{- Programming stage }
declare-unique : Name โ (RGB โ Set) โ RGB โ TC โค
declare-unique it S colour =
= do โฏ
{- Unquotation stage -}
unquoteDecl red-unique = declare-unique red-unique IsRed Red
unquoteDecl green-unique = declare-unique green-unique IsGreen Green
unquoteDecl blue-unique = declare-unique blue-unique IsBlue Blue
{- Test -}
_ : โ {c} โ IsGreen c โ c โก Green
_ = green-unique
# Sidequest: Avoid tedious `refl` proofs
Time for a breather (โขฬแดโขฬ)ู
Look around your code base for a function that makes explicit pattern matching, such as:
just-Red : RGB โ RGB
just-Red Red = Red
just-Red Green = Red
just-Red Blue = Red
only-Blue : RGB โ RGB
only-Blue Blue = Blue
only-Blue _ = Blue
Such functions have properties which cannot be proven unless we pattern match
on the arguments they pattern match. For example, that the above function is
constantly `Red` requires pattern matching then a `refl` for each clause.
just-Red-is-constant : โ{c} โ just-Red c โก Red
just-Red-is-constant {Red} = refl
just-Red-is-constant {Green} = refl
just-Red-is-constant {Blue} = refl
{- Yuck, another tedious proof -}
only-Blue-is-constant : โ{c} โ only-Blue c โก Blue
only-Blue-is-constant {Blue} = refl
only-Blue-is-constant {Red} = refl
only-Blue-is-constant {Green} = refl
In such cases, we can encode the general design decisions ---*pattern match and yield refl*โ
then apply the schema to each use case.
Here's the schema:
constructors : Definition โ List Name
constructors (data-type pars cs) = cs
constructors _ = []
by-refls : Name โ Term โ TC โค
by-refls nom thm-you-hope-is-provable-by-refls
= let mk-cls : Name โ Clause
mk-cls qcolour = clause [ ๐ฝ๐๐ถ (con qcolour []) ] (con (quote refl) [])
in
do let ฮท = nom
ฮด โ getDefinition (quote RGB)
let clauses = List.map mk-cls (constructors ฮด)
declareDef (๐๐๐ถ ฮท) thm-you-hope-is-provable-by-refls
defineFun ฮท clauses
Here's a use case.
_ : โ{c} โ just-Red c โก Red
_ = nice
where unquoteDecl nice = by-refls nice (quoteTerm (โ{c} โ just-Red c โก Red))
Note:
1. The first `nice` refers to the function
created by the RHS of the unquote.
2. The RHS `nice` refers to the Name value provided
by the LHS.
3. The LHS `nice` is a declaration of a Name value.
This is rather clunky since the theorem to be proven was repeated twice
โrepetition is a signal that something's wrong! In the next section we
use macros to avoid such repetiton, as well as the `quoteTerm` keyword.
Note that we use a `where` clause since unquotation cannot occur in a `let`,
for some reason.
Here's another use case of the proof pattern (โขฬแดโขฬ)ู
_ : โ{c} โ only-Blue c โก Blue
_ = nice
where unquoteDecl nice = by-refls nice (quoteTerm โ{c} โ only-Blue c โก Blue)
One proof pattern, multiple invocations!
Super neat stuff :grin:
# Macros โAbstracting Proof Patterns
Macros are functions of type `ฯโ โ ฯโ โ โฏ โ Term โ TC โค` that are defined in a
`macro` block. The last argument is supplied by the type checker and denotes
the โgoalโ of where the macro is placed: One generally unifies what they have
with the goal, what is desired in the use site.
Why the `macro` block?
- Metaprograms can be run in a term position.
- Without the macro block, we run computations using the `unquote` keyword.
- Quotations are performed automatically; e.g.,
if `f : Term โ Name โ Bool โ Term โ TC โค`
then an application `f u v w` desugars into
`unquote (f (quoteTerm u) (quote v) w)`.
No syntactic overhead: Macros are applied like normal functions.
Macros cannot be recursive; instead one defines a recursive function outside the
macro block then has the macro call the recursive function.
## C-style macros
In the C language one defines a macro, say, by `#define luckyNum 1972` then later uses
it simply by the name `luckyNum`. Without macros, we have syntactic overhead using
the `unquote` keyword:
luckyNumโ : Term โ TC โค
luckyNumโ h = unify h (quoteTerm 55)
numโ : โ
numโ = unquote luckyNumโ
Instead, we can achieve C-style behaviour by placing our metaprogramming code within a `macro` block.
macro
luckyNum : Term โ TC โค
luckyNum h = unify h (quoteTerm 55)
num : โ
num = luckyNum
Unlike C, all code fragments must be well-defined.
**Exercise:** Write a macro to always yield the first argument in a function.
The second example shows how it can be used to access implicit arguments
without mentioning them :b
macro
first : Term โ TC โค
first goal = โฏ
myconst : {A B : Set} โ A โ B โ A
myconst = ฮป x โ ฮป y โ first
mysum : ({x} y : โ) โ โ
mysum y = y + first
C-style macros โunifying against a concretely quoted termโ are helpful
when learning reflection. For example, define a macro `use` that yields
different strings according to the shape of their input โthis exercise
increases familiarity with the `Term` type. Hint: Pattern match on the
first argument ;-)
macro
use : Term โ Term โ TC โค
use = โฏ
{- Fully defined, no arguments. -}
2+2โ4 : 2 + 2 โก 4
2+2โ4 = refl
_ : use 2+2โ4 โก "Nice"
_ = refl
{- โpโ has arguments. -}
_ : {x y : โ} {p : x โก y} โ use p โก "WoahThere"
_ = refl
## Tedious Repetitive Proofs No More!
Suppose we wish to prove that addition, multiplication, and exponentiation
have right units 0, 1, and 1 respectively. We obtain the following nearly identical
proofs!
+-rid : โ{n} โ n + 0 โก n
+-rid {zero} = refl
+-rid {suc n} = cong suc +-rid
*-rid : โ{n} โ n * 1 โก n
*-rid {zero} = refl
*-rid {suc n} = cong suc *-rid
^-rid : โ{n} โ n ^ 1 โก n
^-rid {zero} = refl
^-rid {suc n} = cong suc ^-rid
There is clearly a pattern here screaming to be abstracted, let's comply โฅโฟโฅ
The natural course of action in a functional language is to try a higher-order combinator:
{- โfor loopsโ or โInduction for โโ -}
foldn : (P : โ โ Set) (base : P zero) (ind : โ n โ P n โ P (suc n))
โ โ(n : โ) โ P n
foldn P base ind zero = base
foldn P base ind (suc n) = ind n (foldn P base ind n)
Now the proofs are shorter:
_ : โ (x : โ) โ x + 0 โก x
_ = foldn _ refl (ฮป _ โ cong suc) {- This and next two are the same -}
_ : โ (x : โ) โ x * 1 โก x
_ = foldn _ refl (ฮป _ โ cong suc) {- Yup, same proof as previous -}
_ : โ (x : โ) โ x ^ 1 โก x
_ = foldn _ refl (ฮป _ โ cong suc) {- No change, same proof as previous -}
Unfortunately, we are manually copy-pasting the same proof *pattern*.
> When you see repetition, copy-pasting, know that there is room for improvement! (โขฬแดโขฬ)ู
>
> Don't repeat yourself!
Repetition can be mitigated a number of ways, including typeclasses or metaprogramming, for example.
The latter requires possibly less thought and it's the topic of this article, so let's do that :smile:
**Exercise**: Following the template of the previous exercises, fill in the missing parts below.
Hint: It's nearly the same level of difficulty as the previous exercises.
make-rid : (let A = โ) (_โ_ : A โ A โ A) (e : A) โ Name โ TC โค
make-rid _โ_ e nom
= do โฏ
_ : โ{x : โ} โ x + 0 โก x
_ = nice where unquoteDecl nice = make-rid _+_ 0 nice
There's too much syntactic overhead here, let's use macros instead.
macro
_trivially-has-rid_ : (let A = โ) (_โ_ : A โ A โ A) (e : A) โ Term โ TC โค
_trivially-has-rid_ _โ_ e goal
= do ฯ โ quoteTC (ฮป(x : โ) โ x โ e โก x)
unify goal (def (quote foldn) {- Using foldn -}
( ๐๐๐ถ ฯ {- Type P -}
โท ๐๐๐ถ (con (quote refl) []) {- Base case -}
โท ๐๐๐ถ (ฮป๐ "_" โฆ quoteTerm (cong suc)) {- Inductive step -}
โท []))
Now the proofs have minimal repetition *and* the proof pattern is written only *once*:
_ : โ (x : โ) โ x + 0 โก x
_ = _+_ trivially-has-rid 0
_ : โ (x : โ) โ x * 1 โก x
_ = _*_ trivially-has-rid 1
_ : โ (x : โ) โ x * 1 โก x
_ = _^_ trivially-has-rid 1
Note we could look at the type of the goal, find the operator `_โ_` and the unit;
they need not be passed in. Later we will see how to reach into the goal type
and pull pieces of it out for manipulation (โขฬแดโขฬ)ู
It would have been ideal if we could have defined our macro without using `foldn`;
I could not figure out how to do that. ๐ง
Before one abstracts a pattern into a macro, it's useful to have a few instances
of the pattern beforehand. When abstracting, one may want to compare how we think
versus how Agda's thinking. For example, you may have noticed that in the previous
macro, Agda normalised the expression `suc n + 0` into `suc (n + 0)` by invoking the definition
of `_+_`. We may inspect the goal of a function with the `quoteGoal โฏ in โฏ` syntax:
+-ridโฒ : โ{n} โ n + 0 โก n
+-ridโฒ {zero} = refl
+-ridโฒ {suc n} = quoteGoal e in
let
suc-n : Term
suc-n = con (quote suc) [ ๐๐๐ถ (var 0 []) ]
lhs : Term
lhs = def (quote _+_) (๐๐๐ถ suc-n โท ๐๐๐ถ (lit (nat 0)) โท [])
{- Check our understanding of what the goal is โeโ. -}
_ : e โก def (quote _โก_)
(๐ฝ๐๐ถ (quoteTerm Level.zero) โท ๐ฝ๐๐ถ (quoteTerm โ)
โท ๐๐๐ถ lhs โท ๐๐๐ถ suc-n โท [])
_ = refl
{- What does it look normalised. -}
_ : quoteTerm (suc (n + 0) โก n)
โก unquote ฮป goal โ (do g โ normalise goal; unify g goal)
_ = refl
in
cong suc +-ridโฒ
It would be really nice to simply replace the last line by a macro, say `induction`.
Unfortunately, for that I would need to obtain the name `+-ridโฒ`, which as far as I could
tell is not possible with the current reflection mechanism.
# Our First Real Proof Tactic
When we have a proof `p : x โก y` it is a nuisance to have to write `sym p` to prove `y โก x`
โwe have to remember which โdirectionโ `p`. Let's alleviate such a small burden, then use
the tools here to alleviate a larger burden later; namely, rewriting subexpressions.
Given `p : x โก y`, we cannot simply yield `def (quote sym) [ ๐๐๐ถ p ]` since `sym` actually
takes four arguments โcompare when we quoted `_โก_` earlier. Instead, we infer type of `p`
to be, say, `quoteTerm (_โก_ {โ} {A} x y)`. Then we can correctly provide all the required arguments.
โก-type-info : Term โ TC (Arg Term ร Arg Term ร Term ร Term)
โก-type-info (def (quote _โก_) (๐ โท ๐ฏ โท arg _ l โท arg _ r โท [])) = returnTC (๐ , ๐ฏ , l , r)
โก-type-info _ = typeError [ strErr "Term is not a โก-type." ]
What if later we decided that we did not want a proof of `x โก y`, but rather of `x โก y`.
In this case, the orginal proof `p` suffices. Rather than rewriting our proof term, our
macro could try providing it if the symmetry application fails.
{- Syntactic sugar for trying a computation, if it fails then try the other one -}
try-fun : โ {a} {A : Set a} โ TC A โ TC A โ TC A
try-fun = catchTC
syntax try-fun t f = try t or-else f
With the setup in hand, we can now form our macro:
macro
applyโ : Term โ Term โ TC โค
applyโ p goal = try (do ฯ โ inferType p
๐ , ๐ฏ , l , r โ โก-type-info ฯ
unify goal (def (quote sym) (๐ โท ๐ฏ โท ๐ฝ๐๐ถ l โท ๐ฝ๐๐ถ r โท ๐๐๐ถ p โท [])))
or-else
unify goal p
For example:
postulate ๐ ๐ : โ
postulate ๐ : ๐ + 2 โก ๐
{- Same proof yields two theorems! (เธเฒ _เฒ )เธ -}
_ : ๐ โก ๐ + 2
_ = applyโ ๐
_ : ๐ + 2 โก ๐
_ = applyโ ๐
Let's furnish ourselves with the ability to inspect the *produced* proofs.
{- Type annotation -}
syntax has A a = a โถ A
has : โ (A : Set) (a : A) โ A
has A a = a
We are using the โghost colonโ obtained with input `\:`.
Let's try this on an arbitrary type:
woah : {A : Set} (x y : A) โ x โก y โ (y โก x) ร (x โก y)
woah x y p = applyโ p , applyโ p
where -- Each invocation generates a different proof, indeed:
first-pf : (applyโ p โถ (y โก x)) โก sym p
first-pf = refl
second-pf : (applyโ p โถ (x โก y)) โก p
second-pf = refl
It is interesting to note that on non โก-terms, `applyโ` is just a no-op.
Why might this be the case?
_ : โ {A : Set} {x : A} โ applyโ x โก x
_ = refl
_ : applyโ "huh" โก "huh"
_ = refl
**Exercise:** When we manually form a proof invoking symmetry we simply write, for example, `sym p`
and the implict arguments are inferred. We can actually do the same thing here! We were a bit dishonest above. ๐
Rewrite `applyโ`, call it `applyโ, so that the ~try` block is a single, unparenthesised, `unify` call.
**Exercise:** Extend the previous macro so that we can prove statements of the form `x โก x` regardless of what `p`
proves. Aesthetics hint: `try_or-else_` doesn't need brackets in this case, at all.
macro
applyโ : Term โ Term โ TC โค
applyโ p goal = โฏ
yummah : {A : Set} {x y : A} (p : x โก y) โ x โก y ร y โก x ร y โก y
yummah p = applyโ p , applyโ p , applyโ p
**Exercise:** Write the following seemingly silly macro.
Hint: You cannot use the `โก-type-info` method directly, instead you must invoke `getType` beforehand.
โก-type-infoโฒ : Name โ TC (Arg Term ร Arg Term ร Term ร Term)
โก-type-infoโฒ = โฏ
macro
sumSides : Name โ Term โ TC โค
sumSides n goal = โฏ
_ : sumSides ๐ โก ๐ + 2 + ๐
_ = refl
**Exercise:** Write two macros, `left` and `right`, such that
`sumSides q โก left q + right q`, where `q` is a known name.
These two macros provide the left and right hand sides of the
โก-term they are given.
# Heuristic for Writing a Macro
I have found the following stepwise refinement approach to be useful in constructing
macros. โTest Driven Development in a proof-centric settingโ
1. Write a no-op macro: `mymacro p goal = unify p goal`.
2. Write the test case `mymacro p โก p`.
3. Feel good, you've succeeded.
4. Alter the test ever so slightly to become closer to your goal.
5. The test now breaks, go fix it.
6. Go to step 3.
For example, suppose we wish to consider proofs `p` of expressions of the form `h x โก y`
and our macro is intended to obtain the function `h`. We proceed as follows:
1. Postulate `x, y, h, p` so the problem is well posed.
2. Use the above approach to form a no-op macro.
3. Refine the test to `mymacro p โก ฮป e โ 0` and refine the macro as well.
4. Refine the test to `mymacro p โก ฮป e โ e` and refine the macro as well.
5. Eventually succeeded in passing the desired test `mymacro p โก ฮป e โ h e`
โthen eta reduce.
Along the way, it may be useful to return the *string* name of `h`
or rewrite the test as `_โก_ {Level.zero} {โ โ โ} (mymacro p) โก โฏ`.
This may provide insight on how to repair or continue with macro construction.
6. Throw away the postultes, one at a time, making them arguments declared in the test;
refine macro each time so the test continues to pass as each postulate is removed.
Each postulate removal may require existing helper functions to be altered.
7. We have considered function applications, then variable funcctions, finally
consider constructors. Ensure tests cover all these, for this particular problem.
**Exercise:** Carry this through to produce the above discussed example macro, call it `โก-head`. To help you on your
way, here is a useful function:
{- If we have โf $ argsโ return โfโ. -}
$-head : Term โ Term
$-head (var v args) = var v []
$-head (con c args) = con c []
$-head (def f args) = def f []
$-head (pat-lam cs args) = pat-lam cs []
$-head t = t
With the ability to obtain functions being applied in propositional equalities,
we can now turn to lifiting a proof from `x โก y` to suffice proving `f x โก f y`.
We start with the desired goal and use the stepwise refinement approach outlined
earlier to arrive at:
macro
applyโ : Term โ Term โ TC โค
applyโ p goal = try (do ฯ โ inferType goal
_ , _ , l , r โ โก-type-info ฯ
unify goal ((def (quote cong) (๐๐๐ถ ($-head l) โท ๐๐๐ถ p โท []))))
or-else unify goal p
_ : โ {x y : โ} {f : โ โ โ} (p : x โก y) โ f x โก f y
_ = ฮป p โ applyโ p
_ : โ {x y : โ} {f g : โ โ โ} (p : x โก y)
โ x โก y
-- โ f x โก g y {- โapplyโ pโ now has a unification error ^_^ -}
_ = ฮป p โ applyโ p
# What about somewhere deep within a subexpression?
Consider,
suc X + (X * suc X + suc X)
โกโจ cong (ฮป it โ suc X + it) (+-suc _ _) โฉ
suc X + suc (X * suc X + X)
Can we find `(ฮป it โ suc X + it)` mechanically ;-)
Using the same refinement apporach outlined earlier, we begin with the following
working code then slowly, one piece at a time, replace the whole thing with an
`unquote (unify (quoteTerm โฏworkingCodeHereโฏ))`. Then we push the `quoteTerm`
further in as much as possible and construct the helper functions to make
this transation transpire.
open import Data.Nat.Properties
{- +-suc : โ m n โ m + suc n โก suc (m + n) -}
testโ : โ {m n k : โ} โ k + (m + suc n) โก k + suc (m + n)
testโ {m} {n} {k} = cong (k +_) (+-suc m n)
Let's follow the aforementioned approach by starting out with some postulates.
postulate ๐ณ : โ
postulate ๐ข : suc ๐ณ + (๐ณ * suc ๐ณ + suc ๐ณ) โก suc ๐ณ + suc (๐ณ * suc ๐ณ + ๐ณ)
๐ฎ๐ณ : Arg Term
๐ฎ๐ณ = ๐๐๐ถ (con (quote suc) [ ๐๐๐ถ (quoteTerm ๐ณ) ])
๐ขหก ๐ขสณ : Term
๐ขหก = def (quote _+_) (๐ฎ๐ณ โท ๐๐๐ถ (def (quote _+_) (๐๐๐ถ (def (quote _*_) (๐๐๐ถ (quoteTerm ๐ณ) โท ๐ฎ๐ณ โท [])) โท ๐ฎ๐ณ โท [])) โท [])
๐ขสณ = def (quote _+_) (๐ฎ๐ณ โท ๐๐๐ถ (con (quote suc) [ ๐๐๐ถ (def (quote _+_) (๐๐๐ถ (def (quote _*_) (๐๐๐ถ (quoteTerm ๐ณ) โท ๐ฎ๐ณ โท [])) โท ๐๐๐ถ (quoteTerm ๐ณ) โท [])) ]) โท [])
It seems that the left and right sides of ๐ข โmeetโ at `def (quote _+_) (๐ฎ๐ณ โท [])`:
We check the equality of the quoted operator, `_+_`, then recursively check the arguments.
Whence the following naive algorithm:
{- Should definitely be in the standard library -}
โ_โ : โ {a} {A : Set a} โ Dec A โ Bool
โ yes p โ = true
โ no ยฌp โ = false
import Agda.Builtin.Reflection as Builtin
_$-โ_ : Term โ Term โ Bool
con c args $-โ con cโฒ argsโฒ = Builtin.primQNameEquality c cโฒ
def f args $-โ def fโฒ argsโฒ = Builtin.primQNameEquality f fโฒ
var x args $-โ var xโฒ argsโฒ = โ x Nat.โ xโฒ โ
_ $-โ _ = false
{- Only gets heads and as much common args, not anywhere deep. :'( -}
infix 5 _โ_
{-# TERMINATING #-} {- Fix this by adding fuel (con c args) โ 1 + length args -}
_โ_ : Term โ Term โ Term
l โ r with l $-โ r | l | r
...| false | x | y = unknown
...| true | var f args | var fโฒ argsโฒ = var f (List.zipWith (ฮป{ (arg i!! t) (arg j!! s) โ arg i!! (t โ s) }) args argsโฒ)
...| true | con f args | con fโฒ argsโฒ = con f (List.zipWith (ฮป{ (arg i!! t) (arg j!! s) โ arg i!! (t โ s) }) args argsโฒ)
...| true | def f args | def fโฒ argsโฒ = def f (List.zipWith (ฮป{ (arg i!! t) (arg j!! s) โ arg i!! (t โ s) }) args argsโฒ)
...| true | ll | _ = ll {- Left biased; using โunknownโ does not ensure idempotence. -}
The bodies have names involving `!!`, this is to indicate a location of improvement.
Indeed, this naive algorithm ignores visibility and relevance of arguments โfar from ideal.
Joyously this works! ๐
_ : ๐ขหก โ ๐ขสณ โก def (quote _+_) (๐ฎ๐ณ โท ๐๐๐ถ unknown โท [])
_ = refl
{- test using argument function ๐ถ and argument number X -}
_ : {X : โ} {๐ถ : โ โ โ}
โ
let gl = quoteTerm (๐ถ X + (X * ๐ถ X + ๐ถ X))
gr = quoteTerm (๐ถ X + ๐ถ (X * ๐ถ X + X))
in gl โ gr โก def (quote _+_) (๐๐๐ถ (var 0 [ ๐๐๐ถ (var 1 []) ]) โท ๐๐๐ถ unknown โท [])
_ = refl
The `unknown` terms are far from desirable โwe ought to replace them with sections; i.e., an anonoymous lambda.
My naive algorithm to achieve a section from a term containing โunknownโs is as follows:
1. Replace every `unknown` with a De Bruijn index.
2. Then, find out how many unknowns there are, and for each, stick an anonoymous lambda at the front.
- Sticking a lambda at the front breaks existing De Bruijn indices, so increment them for each lambda.
There is clear inefficiency here, but I'm not aiming to be efficient, just believable to some degree.
{- โunknownโ goes to a variable, a De Bruijn index -}
unknown-elim : โ โ List (Arg Term) โ List (Arg Term)
unknown-elim n [] = []
unknown-elim n (arg i unknown โท xs) = arg i (var n []) โท unknown-elim (n + 1) xs
unknown-elim n (arg i (var x args) โท xs) = arg i (var (n + suc x) args) โท unknown-elim n xs
unknown-elim n (arg i x โท xs) = arg i x โท unknown-elim n xs
{- Essentially we want: body(unknownแตข) โ ฮป _ โ body(var 0)
However, now all โvar 0โ references in โbodyโ refer to the wrong argument;
they now refer to โone more lambda away than beforeโ. -}
unknown-count : List (Arg Term) โ โ
unknown-count [] = 0
unknown-count (arg i unknown โท xs) = 1 + unknown-count xs
unknown-count (arg i _ โท xs) = unknown-count xs
unknown-ฮป : โ โ Term โ Term
unknown-ฮป zero body = body
unknown-ฮป (suc n) body = unknown-ฮป n (ฮป๐ "section" โฆ body)
{- Replace โunknownโ with sections -}
patch : Term โ Term
patch it@(def f args) = unknown-ฮป (unknown-count args) (def f (unknown-elim 0 args))
patch it@(var f args) = unknown-ฮป (unknown-count args) (var f (unknown-elim 0 args))
patch it@(con f args) = unknown-ฮป (unknown-count args) (con f (unknown-elim 0 args))
patch t = t
Putting meet, `_โ_`, and this `patch` together into a macro:
macro
spine : Term โ Term โ TC โค
spine p goal
= do ฯ โ inferType p
_ , _ , l , r โ โก-type-info ฯ
unify goal (patch (l โ r))
The expected tests pass โso much joy :joy:
_ : spine ๐ข โก suc ๐ณ +_
_ = refl
module testing-postulated-functions where
postulate ๐ถ : โ โ โ
postulate _๐ท_ : โ โ โ โ โ
postulate ๐ฐ : ๐ถ ๐ณ ๐ท ๐ณ โก ๐ถ ๐ณ ๐ท ๐ถ ๐
_ : spine ๐ฐ โก (๐ถ ๐ณ ๐ท_)
_ = refl
_ : {X : โ} {G : suc X + (X * suc X + suc X) โก suc X + suc (X * suc X + X)}
โ quoteTerm G โก var 0 []
_ = refl
The tests for `โก-head` still go through using `spine`
which can thus be thought of as a generalisation ;-)
Now the original problem is dealt with as a macro:
macro
applyโ
: Term โ Term โ TC โค
applyโ
p hole
= do ฯ โ inferType hole
_ , _ , l , r โ โก-type-info ฯ
unify hole ((def (quote cong)
(๐๐๐ถ (patch (l โ r)) โท ๐๐๐ถ p โท [])))
Curious, why in the following tests we cannot simply use `+-suc _ _`?
_ : suc ๐ณ + (๐ณ * suc ๐ณ + suc ๐ณ) โก suc ๐ณ + suc (๐ณ * suc ๐ณ + ๐ณ)
_ = applyโ
(+-suc (๐ณ * suc ๐ณ) ๐ณ)
test : โ {m n k : โ} โ k + (m + suc n) โก k + suc (m + n)
test {m} {n} {k} = applyโ
(+-suc m n)
This is super neat stuff ^\_^