-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
def: macros for creating copattern definitions (#376)
There are a couple of places in the 1Lab where we manually eta-expand out copattern definitions by hand to get better goal display. This mostly comes up when dealing with things like subcategories/forgetful functors; Agda will very happily unfold your nicely named category into `Restrict Blah`, which is not particularly helpful! Manually performing these copattern expansions is a bit of a pain, so this PR adds a small macro that performs this mechanical busywork for us. This removes the need for the `declare-concrete-category` macro in #375.
- Loading branch information
Showing
16 changed files
with
368 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
open import 1Lab.Reflection.Signature | ||
open import 1Lab.Reflection.Subst | ||
open import 1Lab.Reflection | ||
open import 1Lab.Path | ||
open import 1Lab.Type | ||
|
||
module 1Lab.Reflection.Copattern where | ||
|
||
-------------------------------------------------------------------------------- | ||
-- Macros for manipulating copattern definitions. | ||
|
||
-- Make a top-level copattern binding for an existing record. | ||
make-copattern : Bool → Name → Term → Term → TC ⊤ | ||
make-copattern declare? def-name tm tp = do | ||
-- Ensure that codomain is a record type. | ||
let tele , cod-tp = pi-view tp | ||
def rec-name params ← pure cod-tp | ||
where _ → typeError [ "make-copattern: codomain of " , termErr tp , " is not a record type." ] | ||
|
||
let inst-tm = apply-tm* tm (tel→args 0 tele) | ||
|
||
-- Construct copattern clauses for each field. | ||
ctor , fields ← get-record-type rec-name | ||
clauses ← | ||
in-context (reverse tele) $ | ||
for fields λ (arg field-info field-name) → do | ||
-- Infer the type of the field with all known arguments instantiated. | ||
field-tp ← infer-type (def field-name (argN inst-tm ∷ [])) | ||
|
||
-- Agda will insert implicits when defining copatterns even | ||
-- with 'withExpandLast true', so we need to do implicit instantiation | ||
-- by hand. There are also cases where it's better to fully | ||
-- eta-expand than not (e.g. the 'helper' we're expanding has a | ||
-- field defined by lazy matching, which does not reduce unless | ||
-- applied, and would cause duplication of the big input term). So | ||
-- we fully eta-expand clauses here. | ||
-- First, we strip off all leading quantifiers from the field | ||
-- type. | ||
let | ||
(field-tele , tp) = pi-view field-tp | ||
nargs = length field-tele | ||
clause-tele = tele ++ field-tele | ||
|
||
-- Construct the pattern portion of the clause, making sure to | ||
-- bind all variables. Note that copattern projections are always | ||
-- visible. | ||
let | ||
pat = tel→pats nargs tele ++ | ||
arg (set-visibility visible field-info) (proj field-name) ∷ | ||
tel→pats 0 field-tele | ||
|
||
-- Construct the body of the clause, making sure to apply all | ||
-- arguments bound outside the copattern match, and apply the | ||
-- eta-expanded arguments. We also need to apply all of the | ||
-- implicit arguments to 'tm'. | ||
body ← | ||
in-context (reverse clause-tele) $ | ||
reduce (def field-name (raise nargs inst-tm v∷ tel→args 0 field-tele)) | ||
|
||
-- Construct the final clause. | ||
pure $ clause clause-tele pat body | ||
|
||
-- Define a copattern binding, and predeclare its type if required. | ||
case declare? of λ where | ||
true → declare (argN def-name) tp <|> pure tt | ||
false → pure tt | ||
|
||
-- Construct the final copattern. | ||
define-function def-name clauses | ||
|
||
-- Repack a record type. | ||
-- Will also accept functions into record types like `A → Record`, | ||
-- and will perform a pointwise repacking. | ||
repack-record : Term → Term → TC Term | ||
repack-record tm tp = do | ||
-- Ensure that codomain is a record type. | ||
tele , cod-tp ← pi-view <$> reduce tp | ||
def rec-name params ← pure cod-tp | ||
where _ → typeError [ "repack-record: codomain of " , termErr tp , " is not a record type." ] | ||
|
||
-- Instantiate the term with all telescope arguments to saturate it. | ||
let inst-tm = apply-tm* tm (tel→args 0 tele) | ||
|
||
-- Construct fields for each projection. | ||
ctor , fields ← get-record-type rec-name | ||
args ← | ||
in-context (reverse tele) $ | ||
for fields λ (arg field-info field-name) → | ||
argN <$> reduce (def field-name (argN inst-tm ∷ [])) | ||
|
||
-- Builld a pointwise repacking. | ||
pure (tel→lam tele (con ctor args)) | ||
|
||
-- Helper for the 'define' macros; Unifies the given goal with the type | ||
-- of the given function, if it has been defined. If the function has | ||
-- not been defined, and the first argument is 'false', then an error is | ||
-- raised. | ||
type-for : String → Bool → Name → Term → TC ⊤ | ||
type-for tac decl? fun goal with decl? | ||
... | true = (unify goal =<< get-type fun) <|> pure tt | ||
... | false = (unify goal =<< get-type fun) <|> typeError | ||
[ "define-" , strErr tac , ": the function " , nameErr fun , " should already have been declared." | ||
] | ||
|
||
-------------------------------------------------------------------------------- | ||
-- Usage | ||
|
||
{- | ||
Write the following in a module: | ||
> unquoteDecl copat = declare-copattern copat thing-to-be-expanded | ||
If you wish to give the binding a type annotation, you can also use | ||
> copat : Your-type | ||
> unquoteDecl copat = declare-copattern copat thing-to-be-expanded | ||
Note that, in this case, the thing-to-be-expanded must have exactly the | ||
same type as the binding `copat`. All features of non-recursive records | ||
are supported, including instance fields and fields with implicit | ||
arguments. | ||
These macros also allow you to lift functions 'A → some-record-type' | ||
into copattern definitions. Note that Agda will generate meta for | ||
implicits before performing quotation, so we need to explicitly | ||
bind all implicits using a lambda before quotation! | ||
-} | ||
|
||
declare-copattern : ∀ {ℓ} {A : Type ℓ} → Name → A → TC ⊤ | ||
declare-copattern {A = A} nm x = do | ||
`x ← quoteTC x | ||
`A ← quoteTC A | ||
make-copattern true nm `x `A | ||
|
||
define-copattern | ||
: ∀ {ℓ} (nm : Name) | ||
→ {@(tactic (type-for "copattern" true nm)) A : Type ℓ} | ||
→ A → TC ⊤ | ||
define-copattern nm {A = A} x = do | ||
`A ← quoteTC A | ||
`x ← define-abbrev nm "value" `A =<< quoteTC x | ||
make-copattern false nm `x `A | ||
|
||
{- | ||
There is a slight caveat with level-polymorphic defintions, as | ||
they cannot be quoted into any `Type ℓ`. With this in mind, | ||
we also provide a pair of macros that work over `Typeω` instead. | ||
-} | ||
|
||
declare-copatternω : ∀ {U : Typeω} → Name → U → TC ⊤ | ||
declare-copatternω nm A = do | ||
`A ← quoteωTC A | ||
-- Cannot quote things in type Typeω, but we can infer their type. | ||
`U ← infer-type `A | ||
make-copattern true nm `A `U | ||
|
||
define-copatternω | ||
: (nm : Name) {@(tactic (type-for "copatternω" false nm)) U : Typeω} | ||
→ U → TC ⊤ | ||
define-copatternω nm A = do | ||
`U ← get-type nm | ||
`A ← define-abbrev nm "value" `U =<< quoteωTC A | ||
make-copattern false nm `A `U | ||
|
||
{- | ||
Another common pattern is that two records `r : R p` and `s : R q` may contain | ||
the same data, but they are parameterized by different values. | ||
The `define-eta-expansion` macro will automatically construct a function | ||
`R p → R q` that eta-expands the first record out into a copattern definition. | ||
-} | ||
|
||
define-eta-expansion : Name → TC ⊤ | ||
define-eta-expansion nm = do | ||
tp ← reduce =<< infer-type (def nm []) | ||
let tele , _ = pi-view tp | ||
let tm = tel→lam tele (var 0 []) | ||
make-copattern false nm tm tp | ||
|
||
-------------------------------------------------------------------------------- | ||
-- Tests | ||
|
||
private module Test where | ||
-- Record type that uses all interesting features. | ||
record Record {ℓ} (A : Type ℓ) : Type ℓ where | ||
no-eta-equality | ||
constructor mk | ||
field | ||
⦃ c ⦄ : A | ||
{ f } : A → A | ||
const : ∀ {x} → f x ≡ c | ||
|
||
-- Record created via a constructor. | ||
via-ctor : Record Nat | ||
via-ctor = mk ⦃ c = 0 ⦄ {f = λ _ → 0} refl | ||
|
||
-- Both macros work. | ||
unquoteDecl copat-decl-via-ctor = declare-copattern copat-decl-via-ctor via-ctor | ||
|
||
copat-def-via-ctor : Record Nat | ||
unquoteDef copat-def-via-ctor = define-copattern copat-def-via-ctor via-ctor | ||
|
||
-- Record created by a function. | ||
module _ (r : Record Nat) where | ||
open Record r | ||
via-function : Record Nat | ||
via-function .c = suc c | ||
via-function .f = suc ∘ f | ||
via-function .const = ap suc const | ||
|
||
-- Also works when applied to the result of a function. | ||
unquoteDecl copat-decl-via-function = declare-copattern copat-decl-via-function (via-function via-ctor) | ||
|
||
-- Test to see how we handle unused parameters. | ||
record Unused (n : Nat) : Type where | ||
field | ||
actual : Nat | ||
|
||
zero-unused-param : Unused 0 | ||
zero-unused-param = record { actual = 0 } | ||
|
||
one-unused-param : ∀ {n} → Unused n | ||
unquoteDef one-unused-param = declare-copattern one-unused-param zero-unused-param | ||
-- This is a type error: | ||
-- unquoteDef one-unused-param = define-copattern one-unused-param zero-unused-param | ||
-- because the 'define' macro propagates the type of the thing being | ||
-- defined inwards. | ||
|
||
-- Functions into records that are universe polymorphic | ||
neat : ∀ {ℓ} {A : Type ℓ} → A → Record A | ||
neat a .Record.c = a | ||
neat a .Record.f _ = a | ||
neat a .Record.const = refl | ||
|
||
-- Implicit insertion is correct for the define- macro, since the type | ||
-- of the function is given. | ||
cool : ∀ {ℓ} {A : Type ℓ} → A → Record A | ||
unquoteDef cool = define-copatternω cool neat | ||
|
||
-- Eta-expanders | ||
expander : ∀ {m n : Nat} → Unused m → Unused n | ||
unquoteDef expander = define-eta-expansion expander | ||
|
||
-- Raises a type error: the function should have a declaration. | ||
-- unquoteDecl uncool = define-copatternω uncool neat |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.