Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Giving access in match to the expansion of the term being matched via an alias in order to support more fixpoints #73

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

herbelin
Copy link
Member

@herbelin herbelin commented Aug 10, 2023

Rendered here.

@herbelin herbelin force-pushed the master+match-with-alias branch from b4abbdb to 0bf5474 Compare September 26, 2023 15:26
Co-authored-by: Paolo G. Giarrusso <[email protected]>
Comment on lines +11 to +63
# Motivation

When writing fixpoints in inductive types with indices, there is a standard conflict between referring to the expansion of a variable being matched (so that its type corresponds to the type in the branch) or referring to the variable so that it is compatible with the guard when the fixpoint is later used in another fixpoint. A typical example (even without indices) is `Nat.sub`:
```coq
Fixpoint sub (n m : nat) {struct n} : nat :=
match n, m with
| S k, S l => sub k l
| _, _ => n
end
```
where it is important that `n` is not expanded.

The proposal is to resolve this conflict by giving an explicit name to the expansion of the term being matched in a branch so that the guard condition knows that it is a decreasing argument and not a constructor disconnected from the term being matched. For instance, `Nat.sub` would be written:
```coq
fix sub (n m : nat) {struct n} : nat :=
match n, m with
| S k, S l => sub k l
| _ as n', _ => n'
end
```

In the case of an inductive type with no indices, this does not provide much, but the situation changes for type families. For instance, imagine we want to apply parametricity to `Nat.sub`. That would give:
```coq
Fixpoint is_sub (n : nat) (Pn : is_nat n) (m : nat) (Pm : is_nat m) : is_nat (Nat.sub n m) :=
match Pn in (is_nat n) return is_nat (Nat.sub n m) with
| is_O => is_O
| is_S x P_ =>
match
Pm in (is_nat m0)
return is_nat (match m0 with 0 => S x | S l => Nat.sub x l end)
with
| is_O => is_S x P_
| is_S l Pl => is_sub x P_ l Pl
end
end.
```
where, for typing, we have to expand `Pn` into a constructor in the right-hand side.

With the proposed extension, we would write:
```coq
Fixpoint is_sub (n : nat) (Pn : is_nat n) (m : nat) (Pm : is_nat m) : is_nat (Nat.sub n m) :=
match Pn in (is_nat n) return is_nat (Nat.sub n m) with
| is_O as p => p
| is_S x P_ as p =>
match
Pm in (is_nat m0)
return is_nat (match m0 with 0 => S x | S l => Nat.sub x l end)
with
| is_O => p
| is_S l Pl => is_sub x P_ l Pl
end
end.
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should be using some sort of convoy pattern?

Fixpoint sub (n m : nat) {struct n} : nat :=
  match n with
  | 0 => fun n => n
  | S k => fun n =>
      match m with
      | 0 => n
      | S l => sub k l
      end
  end n.

Inductive is_nat : nat -> Prop :=
| is_O : is_nat 0
| is_S x : is_nat x -> is_nat (S x).

Fixpoint is_sub (n : nat) (Pn : is_nat n) (m : nat) (Pm : is_nat m) : is_nat (Nat.sub n m) :=
  match Pn in (is_nat n) return is_nat n -> is_nat (Nat.sub n m) with
  | is_O => fun p => p
  | is_S x P_ => fun p =>
      match
        Pm in (is_nat m0)
        return is_nat (match m0 with 0 => S x | S l => Nat.sub x l end)
      with
      | is_O => p
      | is_S l Pl => is_sub x P_ l Pl
      end
  end Pn.

works (not sure if that's exactly what parametricity would produce)

@herbelin
Copy link
Member Author

herbelin commented Dec 6, 2023

@SkySkimmer: Nice! Your "commutative cut" indeed provides a simple encoding of match with the extra as, which works also for the guard thanks to the propagation of the size through commutative cuts.

For the record, I add the working parametricity of (a simplification of) gcd which relies on sub:

Fixpoint G (a b : nat) {struct a} : nat :=
  match a return nat with
  | O => b
  | S a' => G (Nat.sub a' b) (S a')
  end.

Fixpoint is_G a (is_a : is_nat a) b (is_b : is_nat b) {struct is_a} : is_nat (G a b) :=
  match is_a in is_nat a return is_nat (G a b) with
  | is_O => is_b
  | is_S a' P_ => is_G _ (is_sub a' P_ b is_b) (S a') (is_S a' P_)
    (* failed with the variant of is_sub w/o generalization that returns the expansion of Pn *)
  end.

@herbelin
Copy link
Member Author

herbelin commented Dec 6, 2023

I guess this new justification still leads to two possible points of view:

  • it is useless because emulatable,
  • it is emulatable so it is "safe" to conveniently provide this "direct" style.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants