Trouble with successor & predecessor in Mogensen's binary encoding - encoding

I would like to add binary numbers to my untyped lambda calculus library, but I'm stuck with the succ and pred functions. I am using a representation outlined in a paper by T. Mogensen and while most of the functions defined there work, succ and pred are returning erroneous results.
I'm pretty sure I got the representation right:
dec bin De Bruijn classic
0 0 λλλ3 λa.λb.λc.a
1 1 λλλ13 λa.λb.λc.c a
2 10 λλλ2(13) λa.λb.λc.b (c a)
3 11 λλλ1(13) λa.λb.λc.c (c a)
4 100 λλλ2(2(13)) λa.λb.λc.b (b (c a))
5 101 λλλ1(2(13)) λa.λb.λc.c (b (c a))
6 110 λλλ2(1(13)) λa.λb.λc.b (c (c a))
7 111 λλλ1(1(13)) λa.λb.λc.c (c (c a))
8 1000 λλλ2(2(2(13))) λa.λb.λc.b (b (b (c a)))
The tuples and projections also look fine:
tuple De Bruijn classic
[T, F] λ1(λλ2)(λλ1) λa.a (λb.λc.b) (λb.λc.c)
[T, F, F] λ1(λλ2)(λλ1)(λλ1) λa.a (λb.λc.b) (λb.λc.c) (λb.λc.c)
[T, F, F, T] λ1(λλ2)(λλ1)(λλ1)(λλ2) λa.a (λb.λc.b) (λb.λc.c) (λb.λc.c) (λb.λc.b)
πkn De Bruijn classic
π12 λ1(λλ2) λa.a (λb.λc.b)
π22 λ1(λλ1) λa.a (λb.λc.c)
Shifting up with a 0-bit (shl0) and 1-bit (shl1) works well in tests:
SHL0 ≡ λnbzo.z (n b z o) = λ λ λ λ 2 (4 3 2 1)
SHL1 ≡ λnbzo.o (n b z o) = λ λ λ λ 1 (4 3 2 1)
But succ and pred that depend on the terms listed above don't:
SUCC ≡ λn.π22 (n Z A B) ≡ λ π22 (1 Z A B) where
Z ≡ [ZERO, ONE] // encoded like in the first piece of code
A ≡ λp.p (λnm.[SHL0 n, SHL1 n]) ≡ λ 1 (λ λ [SHL0 2, SHL1 2])
B ≡ λp.p (λnm.[SHL1 n, SHL0 m]) ≡ λ 1 (λ λ [SHL1 2, SHL0 1])
PRED ≡ λn.π22 (n Z A B) ≡ λ π22 (1 Z A B) where
Z ≡ [ZERO, ZERO] // encoded like in the first piece of code
A ≡ λp.p (λnm.[SHL0 n, SHL1 m]) ≡ λ 1 (λ λ [SHL0 2, SHL1 1])
B ≡ λp.p (λnm.[SHL1 n, SHL0 n]) ≡ λ 1 (λ λ [SHL1 2, SHL0 2])
Example results:
succ 0 = λa.λb.λc.c a / λλλ13 ok
succ 1 = λa.λb.λc.b (b c) / λλλ2(21) wrong, expected λλλ2(13)
succ 2 = λa.λb.λc.c (b (c (λd.λe.λf.e (b d e f)) (λd.λe.λf.f (b d e f)))) / λλλ1(2(1(λλλ2(5321))(λλλ1(5321)))) wrong, expected λλλ1(13)
succ 3 = λa.λb.λc.b (b c) / λλλ2(21) wrong, expected λλλ2(2(13))
pred 1 = λa.λb.λc.b a / λλλ23 wrong-ish, expected λλλ3; it's just a leading zero, but it's stated that those should only be caused by inputs that are powers of 2
pred 2 = λa.λb.λc.c (b c) / λλλ1(21) wrong, expected λλλ13
pred 3 = λa.λb.λc.b (b a) / λλλ2(23) wrong, expected λλλ2(13)
pred 4 = λa.λb.λc.c (b c) / λλλ1(21) wrong, expected λλλ1(13)
My term evaluator was tested against hundreds of terms, so I'm pretty confident in it; I'm suspecting that either I'm misreading something or something is misprinted. Am I missing something?

So, as ljedrz mentionned, we manage to get Morgensen numerals working in a separate chat. In this answer, I'll just describe shortly how it works in general.
The question was: « I'm suspecting that either I'm misreading something or something is misprinted. Am I missing something? »
tl;dr: it turns out some tricky stuff related to evaluation order caused the problem. The Morgensen numerals as presented in the question do work.
Longer answer: how does succ work ?
N.B.: in the following b_n is always assumed to be 1, as in the original paper.
The idea behind Morgensen numerals is to have a number n = b_n ... b_2 b_1 encoded as \z.\x_0.\x_1. x_{b_1} ( x_{b_2} (... ( x_{b_n} z ) ...) ). This is pretty unreadable, but it becomes clearer if it is stated that way:
A number n is a term which expects 3 arguments, and when applied, returns x_{b_1} ( x_{b_2} (... ( x_{b_n} z ) ...) )
Well, that's still unclear. If we look deeper, we see that a number n apply recursively either x_0 or x_1, starting from a term z. Notice that the recursive call is made "from left to right", i.e., if I have a number b_n b_{n-1} ... b_2 b_1, then the recursive calls are evaluated in this order:
first b_n z, let it be i_{n-1}
then b_{n-1} i_{n-1}, let it be i_{n-2}
...
and finally i_1 b_1
(Well, the evaluation strategy determines the exact evaluation order, to I think it is easy to think it evaluates like that)
Relation with fold function on lists
Actually, when I realised that, it just made me think about the fold_left function of a bit list: say you have a list of bits l = [b_n; ... ; b_2; b_1], then you can do the following:
fold_left (fun prev_acc -> fun b -> if b = 0 then x_0 prev_acc else x_1 prev_acc) z l
let f be
fun prev_acc -> fun b -> if b = 0 then x_0 prev_acc else x_1 prev_acc
which returns (according to Ocaml doc)
f (f (... (f z b_n) ...) b_2) b_1
which evaluates to:
f z b_n evaluates to x_{b_n} z, that is i_{n-1} as above.
...
f i_{1} b_1, as above.
Conclusion, you can absolutely think of Morgensen numerals as a fold_left on a list (or fold_right, depending on how you imagine the list).
Getting the succ of a number
Getting the succ of a number is getting n+1. The binary increment as a nice property:
if m = bn ... bi bj bk ... b1 with bj being the first 0 (i.e. bk = ... = b1 = 1), then m + 1 = bn ... bi 1 0 ... 0
This can be illustrated:
bn ... bi 0 1 1 1 1 1
if I add 1, then I got (by detailling all steps):
bn ... bi 0 1 1 1 1 1
+1
--------------------------
bn ... bi 0 1 1 1 1 0
+1 < I have a carry here, which gets propagated
...
--------------------------
bn ... bi 0 0 0 0 0 0
+1 < The carry ends up here
--------------------------
bn ... bi 1 0 0 0 0 0 < This is our result of doing a +1.
A good remark is to notice that (bn ... bi 0 1 ... 1) + 1 is (bn ... bi 0) + 1 appended to 0 ... 0, and more generally, it also applies for any bj: (bn ... bi bj 1 ... 1) + 1 is (bn ... bi bj) + 1 appended to 0 ... 0.
This seems pretty good, only one problem, the carry is propagated from right to left (LSB to MSB), while Morgensen numerals are from MSB to LSB.
To solve this last problem, we can be speculative: suppose I have a number b_n ... bi bj bk ... b1, and I want to have its successor. I will have to compute it reccursively, but only from MSB to LSB.
That is, if I am at "step bj", I can only work with the subsequence bn ... bi and bj itself.
This allows us, for instance, to compute the succ of bn ... bi. Now comes the speculative part:
I know that, if after bj, there are only 1, then after the previous remark, then the successor is ((bn ... bi bj) + 1)::(0 ... 0)
However, if there is a 0 in bk ... b1, then the bits (bn ... bi bj) remains unchanged.
So the idea is to return both possibilities at each bit, in a tuple. Informally, the function passed to fold_left looks like that:
fun tuple_msb -> fun bj ->
(original_msb, incr_msb)
where (1) tuple_msb is a tuple containing (bn ... bi, (bn ... bi) + 1); and where (2) original_msb and incr_msb are computed depending on bj. Indeed:
if bj is 0, then (bn ... bi bj) + 1 = (bn ... bi 0) + 1 = (bn ... bi 1)
if bj is 1, then (bn ... bi bj) + 1 = (bn ... bi 1) + 1 = ((bn ... bi) + 1)::0.
That is, the complete function to pass to fold_left is the following:
(* We keep the original sequence on the left of the tuple, the incremented `bn ... bi bj` on the right *)
fun tuple_msb -> fun bj ->
if bj = 0 then
(tuple_msb._1 :: 0, tuple_msb._1 :: 1)
else
(tuple_msb._1 :: 1, tuple_msb._2 :: 0)
And the base case (i.e. the starting element is the tuple (0, 1))
And from here, it is easy to go back to Morgensen unreadable terms (there is a small hidden shortcut here about the order of the arguments, but it really doesn't matter):
We can identify fun tuple_msb -> (tuple_msb._1 :: 0, tuple._1 :: 1) as being x_0 and fun tuple_msb -> (tuple_msb._1 :: 1, tuple_msb._2 :: 0) as being x_1 according to the notation we had at the begining for x_0 and x_1, and the base case (i.e. z at the begining is (0, 1)).
To get the final successor, we have to get the right part of the returned tuple, hence the final
let succ n =
let ret_tuple = n z x_0 x_1 in
ret_tuple._2
or in lambda terms:
succ' = λn. π22 (n z x_0 x_1)
with all π22, z, x_0 and x_1 accordingly defined.
Our succ' is a bit different from the proposed succ, i.e. x_0 is not exactly A and x_1 is not exactly B, but this last step is easy and left for the interested reader ;-)

When Bromind informed me that their slightly different definition of succ works in Ocaml+utop I ported it and tested with my library. It failed as well, though, so I started analyzing all the terms involved only to see that everything looked pretty much the same as in my implementation. Convinced that both my evaluator is solid and those definitions are valid, I kept looking until I found the actual cause.
Not to spoil the fun too soon, though, I'll add that I actually contacted prof. Mogensen about this and he was kind enough to provide me with high-level evaluation steps for the expression succ 1:
succ 1
= (λn.π² (n Z A B)) |1|
= (λn.π² (n Z A B)) (λz01.1 z)
= π² ((λz01.1 z) Z A B)
= π² (B Z)
= π² ((λp.p (λnm.[↑1 n, ↑0 m])) [|0|, |1|])
= π² ([|0|, |1|] (λnm.[↑1 n, ↑0 m]))
= π² ((λx.x |0| |1|) (λnm.[↑1 n, ↑0 m]))
= π² ((λnm.[↑1 n, ↑0 m]) |0| |1|)
= π² [↑1 |0|, ↑0 |1|]
= ↑0 |1|
= (λn.λz01.0 (n z 0 1)) |1|
= λz01.0 (|1| z 0 1)
= λz01.0 ((λz01.1 z) z 0 1)
= λz01.0 (1 z)
= |2|
This solution is 100% valid, but it is too high-level to compare with my implementation, so I have decided to detect the exact point where the definition fails with "low-level" β-reductions.
Below is the initial expression (succ 1) in De Bruijn index notation:
(λ(λ1(λλ1))(1(λ1(λλλ3)(λλλ13))(λ1(λλλ1((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1)))))(λλλ13)
and its reduction steps (normal order; others won't work either):
(λ(λ1(λλ1))(1(λ1(λλλ3)(λλλ13))(λ1(λλλ1((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1)))))(λλλ13)
^ ^ ^^^^^^^
↳ becomes λλλ13
(λ1(λλ1))((λλλ13)(λ1(λλλ3)(λλλ13))(λ1(λλλ1((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1))))
^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
↳ becomes (λλλ13)(λ1(λλλ3)(λλλ13))(λ1(λλλ1((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1)))
(λλλ13)(λ1(λλλ3)(λλλ13))(λ1(λλλ1((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1)))(λλ1)
^ ^ ^^^^^^^^^^^^^^^^^
↳ becomes λ1(λλλ3)(λλλ13)
(λλ1(λ1(λλλ3)(λλλ13)))(λ1(λλλ1((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1)))(λλ1)
^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - is dropped
(λ1(λ1(λλλ3)(λλλ13)))(λ1(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1)))(λλ1)
^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
↳ becomes λ1(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1))
(λ1(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1)))(λ1(λλλ3)(λλλ13))(λλ1)
^^ ^^^^^^^^^^^^^^^^^
↳ becomes λ1(λλλ3)(λλλ13)
(λ1(λλλ3)(λλλ13))(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1))(λλ1)
^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
↳ becomes λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1)
(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1))(λλλ3)(λλλ13)(λλ1)
^ ^^^^^^ - is dropped
(λλ1((λλλλ1(4321))2)((λλλλ2(4321))1))(λλλ13)(λλ1)
^ ^ ^^^^^^^
↳ becomes λλλ13
(λ1((λλλλ1(4321))(λλλ13))((λλλλ2(4321))1))(λλ1)
^^ ^
↳ becomes (λλ1) ↳ becomes (λλ1)
(λλ1)((λλλλ1(4321))(λλλ13))((λλλλ2(4321))(λλ1))
^ ^^^^^^^^^^^^^^^^^^^^^^ - is dropped
(λ1)((λλλλ2(4321))(λλ1))
^^ ^^^^^^^^^^^^^^^^^^^^
↳ becomes (λλλλ2(4321))(λλ1)
(λλλλ2(4321))(λλ1)
^ ^ ^^^^^
↳ becomes λλ1
λλλ2((λλ1)321)
^ ^ - is dropped
λλλ2((λ1)21)
^^ ^
↳ becomes 2
λλλ2(21) // fail
It's a subtle one: both A and B need to have their tuple in an unnormalized/destructured form in order for the succ and pred definition to work with pure β-reduction evaluators. I noticed this because my implementation used normalized tuples while Bromind's didn't.
Let's see the correct definition:
(λ(λ1(λλ1))(1(λ1(λλλ3)(λλλ13))(λ1(λλ(λλλ132)((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1)))))(λλλ13)
and its reduction steps (normal order again):
(λ(λ1(λλ1))(1(λ1(λλλ3)(λλλ13))(λ1(λλ(λλλ132)((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1)))))(λλλ13)
^ ^ ^^^^^^^
↳ becomes λλλ13
(λ1(λλ1))((λλλ13)(λ1(λλλ3)(λλλ13))(λ1(λλ(λλλ132)((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1))))
^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
↳ becomes (λλλ13)(λ1(λλλ3)(λλλ13))(λ1(λλ(λλλ132)((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1)))
(λλλ13)(λ1(λλλ3)(λλλ13))(λ1(λλ(λλλ132)((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1)))(λλ1)
^ ^ ^^^^^^^^^^^^^^^^^
↳ becomes λ1(λλλ3)(λλλ13)
(λλ1(λ1(λλλ3)(λλλ13)))(λ1(λλ(λλλ132)((λλλλ2(4321))2)((λλλλ1(4321))2)))(λ1(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1)))(λλ1)
^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - is dropped
(λ1(λ1(λλλ3)(λλλ13)))(λ1(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1)))(λλ1)
^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
↳ becomes λ1(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1))
(λ1(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1)))(λ1(λλλ3)(λλλ13))(λλ1)
^^ ^^^^^^^^^^^^^^^^^
↳ becomes λ1(λλλ3)(λλλ13)
(λ1(λλλ3)(λλλ13))(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1))(λλ1)
^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
↳ becomes λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1)
(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1))(λλλ3)(λλλ13)(λλ1)
^ ^ ^^^^^^
↳ becomes λλλ3
(λ(λλλ132)((λλλλ1(4321))(λλλ3))((λλλλ2(4321))1))(λλλ13)(λλ1)
^ ^ ^^^^^^^
↳ becomes λλλ13
(λλλ132)((λλλλ1(4321))(λλλ3))((λλλλ2(4321))(λλλ13))(λλ1)
^ ^ ^^^^^^^^^^^^^^^^^^^^^
↳ becomes (λλλλ1(4321))(λλλ3)
(λλ1((λλλλ1(4321))(λλλ3))2)((λλλλ2(4321))(λλλ13))(λλ1)
^ ^ ^^^^^^^^^^^^^^^^^^^^^^
↳ becomes (λλλλ2(4321))(λλλ13)
(λ1((λλλλ1(4321))(λλλ3))((λλλλ2(4321))(λλλ13)))(λλ1)
^^ ^^^^^
↳ becomes λλ1
(λλ1)((λλλλ1(4321))(λλλ3))((λλλλ2(4321))(λλλ13))
^ ^^^^^^^^^^^^^^^^^^^^^ - is dropped
(λ1)((λλλλ2(4321))(λλλ13))
^^ ^^^^^^^^^^^^^^^^^^^^^^
↳ becomes (λλλλ2(4321))(λλλ13)
(λλλλ2(4321))(λλλ13)
^ ^ ^^^^^^^
↳ becomes λλλ13
λλλ2((λλλ13)321)
^ ^ ^
↳ becomes the right-hand side 3 and gets an index upgrade due to extra abstractions
λλλ2((λλ15)21)
^ ^
↳ gets its index downgraded
λλλ2((λ14)1)
^^ ^
↳ becomes the right-hand side 1; 4 gets an index downgrade
λλλ2(13) // aww yiss
The crucial difference can be seen below:
(λλλ1((λλλλ1(4321))2)((λλλλ2(4321))1))(λλλ3)(λλλ13)(λλ1) // normalized tuple
^ ^^^^^^ - is dropped
(λλ(λλλ132)((λλλλ1(4321))2)((λλλλ2(4321))1))(λλλ3)(λλλ13)(λλ1) // unnormalized tuple
^ ^ ^^^^^^
↳ becomes λλλ3
If A or B contains a normalized tuple, the zero from the Z tuple is dropped and out of the game, so the crucial step π² ((λnm.[↑1 n, ↑0 m]) |0| |1|) => π² [↑1 |0|, ↑0 |1|] can't fully happen.
Below is the difference between the erroneous (for pure β-reduction evaluators) and correct full (including Z, A, B) definition of succ:
λ PI22 (1 [ZERO, ONE] (λ 1 (λ λ [SHL0 2, SHL1 2])) (λ 1 (λ λ [SHL1 2, SHL0 1]))) // wrong
λ PI22 (1 [ZERO, ONE] (λ 1 (λ λ TUPLE2 (SHL0 2) (SHL1 2))) (λ 1 (λ λ TUPLE2 (SHL1 2) (SHL0 1)))) // right
where TUPLE2 ≡ λ λ λ 1 3 2

Related

Beta expansion in coq: can I make a term into a function, abstracting over another given term?

I want to rewrite a term, as a function in a sort of beta expansion (inverse of beta reduction).
So, for example in the term a + 1 = RHS I would like to replace it as (fun x => x + 1) a = RHS. Obviously, the two terms are equal by betta reduction, but I can't figure out how to automate it.
The tactic pattern comes very close to what I want, except it only applies to a full goal, and I can't see how I would use it in a term inside an equality.
Similarly, I thought I could use the context holes. Here is my best attempt
Ltac betaExpansion term a:=
let T:= type of a in
match term with
context hole [a] =>
idtac hole;
let f:= fun x => context hole [x] in
remember ( fun x:T => f x ) as f'
end.
Goal forall a: nat, a + 1 = 0.
intros a.
match goal with
|- ?LHS = _ =>
betaExpansion LHS a (*Error: Variable f should be bound to a term but is bound to a tacvalue.*)
end.
This obviously fails, because f is a tacvalue when I really need a normal value. Can I somehow evaluate the expression to make it a value?
You should have a look at the pattern tactic. pattern t replaced all occurrences of t in the goal by a beta expanded variable.
You may also use the change ... with ... at tactic.
Goal forall (a:nat) , a+1 = 2* (a+1) - (a+1).
intro x; change (x+1) with ((fun z => z) (x+1)) at 1 3.
(*
x : nat
============================
(fun z : nat => z) (x + 1) = 2 * (x + 1) - (fun z : nat => z) (x + 1)
*)
Or, more automatically
Ltac betaexp term i :=
let x := fresh "x" in
let T := type of term in
change term with ((fun x : T => x) term) at i.
Goal forall (a:nat) , a+1 = a+1 .
intro x; betaexp (x+1) ltac:(1).

Computing with a finite subset of an infinite representation in Coq

I have a function Z -> Z -> whatever which I treat as a sort of a map from (Z, Z) to whatever, let's type it as FF.
With whatever being a simple sum constructible from nix or inj_whatever.
This map I initialize with some data, in the fashion of:
Definition i (x y : Z) (f : FF) : FF :=
fun x' y' =>
if andb (x =? x') (y =? y')
then inj_whatever
else f x y.
The =? represents boolean decidable equality on Z, from Coq's ZArith.
Now I would like to have equality on two of such FFs, I don't mind invoking functional_extensionality. What I would like to do now is to have Coq computationally decide equality of two FFs.
For example, suppose we do something along the lines of:
Definition empty : FF := fun x y => nix.
Now we add some arbitrary values to make foo and foo', those are equivalent under functional extensionality:
Definition foo := i 0 0 (i 0 (-42) (i 56 1 empty)).
Definition foo' := i 0 (-42) (i 56 1 (i 0 0 empty)).
What is a good way to automatically have Coq determine foo = foo'. Ltac level stuff? Actual terminating computation? Do I need domain restriction to a finite one?
The domain restriction is a bit of an intricate one. I manipulate the maps in a way f : FF -> FF, where f can extend the subset of Z x Z that the computation is defined on. As such, come to think of it, it can't be f : FF -> FF, but more like f : FF -> FF_1 where FF_1 is a subset of Z x Z that is extended by a small constant. As such, when one applies f n times, one ends up with FF_n which is equivalent to domain restriction of FF plus n * constant to the domain. So the function f slowly (by a constant factor) expands the domain FF is defined on.
As I said in the comment more specifics are needed in order to elaborate a satisfactory answer. See the below example --- intended for a step by step description --- on how to play with equality on restricted function ranges using mathcomp:
From mathcomp Require Import all_ssreflect all_algebra.
Set Implicit Arguments.
Unset Strict Implicit.
Unset Printing Implicit Defensive.
(* We need this in order for the computation to work. *)
Section AllU.
Variable n : nat.
(* Bounded and unbounded fun *)
Definition FFb := {ffun 'I_n -> nat}.
Implicit Type (f : FFb).
Lemma FFP1 f1 f2 : reflect (f1 = f2) [forall x : 'I_n, f1 x == f2 x].
Proof. exact/(equivP eqfunP)/ffunP. Qed.
Lemma FFP2 f1 f2 :
[forall x : 'I_n, f1 x == f2 x] = all [fun x => f1 x == f2 x] (enum 'I_n).
Proof.
by apply/eqfunP/allP=> [eqf x he|eqf x]; apply/eqP/eqf; rewrite ?enumT.
Qed.
Definition f_inj (f : nat -> nat) : FFb := [ffun x => f (val x)].
Lemma FFP3 (f1 f2 : nat -> nat) :
all [fun x => f1 x == f2 x] (iota 0 n) -> f_inj f1 = f_inj f2.
Proof.
move/allP=> /= hb; apply/FFP1; rewrite FFP2; apply/allP=> x hx /=.
by rewrite !ffunE; apply/hb; rewrite mem_iota ?ltn_ord.
Qed.
(* Exercise, derive bounded eq from f_inj f1 = f_inj f2 *)
End AllU.
The final lemma should indeed allow you reduce equality of functions to a computational, fully runnable Gallina function.
A simpler version of the above, and likely more useful to you is:
Lemma FFP n (f1 f2 : nat -> nat) :
[forall x : 'I_n, f1 x == f2 x] = all [pred x | f1 x == f2 x] (iota 0 n).
Proof.
apply/eqfunP/allP=> eqf x; last by apply/eqP/eqf; rewrite mem_iota /=.
by rewrite mem_iota; case/andP=> ? hx; have /= -> := eqf (Ordinal hx).
Qed.
But it depends on how you (absent) condition on range restriction is specified.
After your edit, I think I should add a note on the more general topic of map equality, indeed you can define a more specific type of maps other than A -> B and then build a decision procedure.
Most typical map types [including the ones in the stdlib] will work, as long as they support the operation of "binding retrieval", so you can reduce equality to the check of finitely-many bound values.
In fact, the maps in Coq's standard library do already provide you such computational equality function.
Ok, this is a rather brutal solution which does not attempt to avoid doing the same case distinctions multiple times but it's fully automated.
We start with a tactic which inspects whether two integers are equal (using Z.eqb) and translates the results to a proposition which omega can deal with.
Ltac inspect_eq y x :=
let p := fresh "p" in
let q := fresh "q" in
let H := fresh "H" in
assert (p := proj1 (Z.eqb_eq x y));
assert (q := proj1 (Z.eqb_neq x y));
destruct (Z.eqb x y) eqn: H;
[apply (fun p => p eq_refl) in p; clear q|
apply (fun p => p eq_refl) in q; clear p].
We can then write a function which fires the first occurence of i it can find. This may introduce contradictory assumptions in the context e.g. if a previous match has revealed x = 0 but we now call inspect x 0, the second branch will have both x = 0 and x <> 0 in the context. It will be automatically dismissed by omega.
Ltac fire_i x y := match goal with
| [ |- context[i ?x' ?y' _ _] ] =>
unfold i at 1; inspect_eq x x'; inspect_eq y y'; (omega || simpl)
end.
We can then put everything together: call functional extensionality twice, repeat fire_i until there's nothing else to inspect and conclude by reflexivity (indeed all the branches with contradictions have been dismissed automatically!).
Ltac eqFF :=
let x := fresh "x" in
let y := fresh "y" in
intros;
apply functional_extensionality; intro x;
apply functional_extensionality; intro y;
repeat fire_i x y; reflexivity.
We can see that it discharges your lemma without any issue:
Lemma foo_eq : foo = foo'.
Proof.
unfold foo, foo'; eqFF.
Qed.
Here is a self-contained gist with all the imports and definitions.

Working with Isabelle's code generator: Data refinement and higher order functions

This is a follow-up on Isabelle's Code generation: Abstraction lemmas for containers?:
I want to generate code for the_question in the following theory:
theory Scratch imports Main begin
typedef small = "{x::nat. x < 10}" morphisms to_nat small
by (rule exI[where x = 0], simp)
code_datatype small
lemma [code abstype]: "small (to_nat x) = x" by (rule to_nat_inverse)
definition a_pred :: "small ⇒ bool"
where "a_pred = undefined"
definition "smaller j = [small i . i <- [0 ..< to_nat j]]"
definition "the_question j = (∀i ∈ set (smaller j). a_pred j)"
The problem is that the equation for smaller is not suitable for code generation, as it mentions the abstraction function small.
Now according to Andreas’ answer to my last question and the paper on data refinement, the next step is to introduce a type for sets of small numbers, and create a definition for smaller in that type:
typedef small_list = "{l. ∀x∈ set l. (x::nat) < 10}" by (rule exI[where x = "[]"], auto)
code_datatype Abs_small_list
lemma [code abstype]: "Abs_small_list (Rep_small_list x) = x" by (rule Rep_small_list_inverse)
definition "smaller' j = Abs_small_list [ i . i <- [0 ..< to_nat j]]"
lemma smaller'_code[code abstract]: "Rep_small_list (smaller' j) = [ i . i <- [0 ..< to_nat j]]"
unfolding smaller'_def
by (rule Abs_small_list_inverse, cases j, auto elim: less_trans simp add: small_inverse)
Now smaller' is executable. From what I understand I need to redefine operations on small list as operations on small_list:
definition "small_list_all P l = list_all P (map small (Rep_small_list l))"
lemma[code]: "the_question j = small_list_all a_pred (smaller' j)"
unfolding small_list_all_def the_question_def smaller'_code smaller_def Ball_set by simp
I can define a good looking code equation for the_question. But the definition of small_list_all is not suitable for code generation, as it mentions the abstraction morphismsmall. How do I make small_list_all executable?
(Note that I cannot unfold the code equation of a_pred, as the problem actually occurs in the code equation of the actually recursive a_pred. Also, I’d like to avoid hacks that involve re-checking the invariant at runtime.)
I don't have a good solution to the general problem, but here's an idea that will let you generate code for the_question in this particular case.
First, define a function predecessor :: "small ⇒ small with an abstract code equation (possibly using lift_definition from λn::nat. n - 1).
Now you can prove a new code equation for smaller whose rhs uses if-then-else, predecessor and normal list operations:
lemma smaller_code [code]:
"smaller j = (if to_nat j = 0 then []
else let k = predecessor j in smaller k # [k])"
(More efficient implementations are of course possible if you're willing to define an auxiliary function.)
Code generation should now work for smaller, since this code equation doesn't use function small.
The short answer is no, it does not work.
The long answer is that there are often workarounds possible. One is shown by Brian in his answer. The general idea seems to be
Separate the function that has the abstract type in covariant positions besides the final return value (i.e. higher order functions or functions returning containers of abstract values) into multiple helper functions so that abstract values are only constructed as a single return value of one of the helper function.
In Brian’s example, this function is predecessor. Or, as another simple example, assume a function
definition smallPrime :: "nat ⇒ small option"
where "smallPrime n = (if n ∈ {2,3,5,7} then Some (small n) else None)"
This definition is not a valid code equation, due to the occurrence of small. But this derives one:
definition smallPrimeHelper :: "nat ⇒ small"
where "smallPrimeHelper n = (if n ∈ {2,3,5,7} then small n else small 0)"
lemma [code abstract]: "to_nat (smallPrimeHelper n) = (if n ∈ {2,3,5,7} then n else 0)"
by (auto simp add: smallPrimeHelper_def intro: small_inverse)
lemma [code_unfold]: "smallPrime n = (if n ∈ {2,3,5,7} then Some (smallPrimeHelper n) else None)"
unfolding smallPrime_def smallPrimeHelper_def by simp
If one wants to avoid the redundant calculation of the predicate (which might be more complex than just ∈ {2,3,5,7}, one can make the return type of the helper smarter by introducing an abstract view, i.e. a type that contains both the result of the computation, and the information needed to construct the abstract type from it:
typedef smallPrime_view = "{(x::nat, b::bool). x < 10 ∧ b = (x ∈ {2,3,5,7})}"
by (rule exI[where x = "(2, True)"], auto)
setup_lifting type_definition_small
setup_lifting type_definition_smallPrime_view
For the view we have a function building it and accessors that take the result apart, with some lemmas about them:
lift_definition smallPrimeHelper' :: "nat ⇒ smallPrime_view"
is "λ n. if n ∈ {2,3,5,7} then (n, True) else (0, False)" by simp
lift_definition smallPrimeView_pred :: "smallPrime_view ⇒ bool"
is "λ spv :: (nat × bool) . snd spv" by auto
lift_definition smallPrimeView_small :: "smallPrime_view ⇒ small"
is "λ spv :: (nat × bool) . fst spv" by auto
lemma [simp]: "smallPrimeView_pred (smallPrimeHelper' n) ⟷ (n ∈ {2,3,5,7})"
by transfer simp
lemma [simp]: "n ∈ {2,3,5,7} ⟹ to_nat (smallPrimeView_small (smallPrimeHelper' n)) = n"
by transfer auto
lemma [simp]: "n ∈ {2,3,5,7} ⟹ smallPrimeView_small (smallPrimeHelper' n) = small n"
by (auto intro: iffD1[OF to_nat_inject] simp add: small_inverse)
With that we can derive a code equation that does the check only once:
lemma [code]: "smallPrime n =
(let spv = smallPrimeHelper' n in
(if smallPrimeView_pred spv
then Some (smallPrimeView_small spv)
else None))"
by (auto simp add: smallPrime_def Let_def)

Cannot understand 'functions as arguments' recursion

I'm taking a functional programming languages course and I'm having difficulty understanding recursion within the context of 'functions as arguments'
fun n_times(f , n , x) =
if n=0
then x
else f (n_times(f , n - 1 , x))
fun double x = x+x;
val x1 = n_times(double , 4 , 7);
the value of x1 = 112
This doubles 'x' 'n' times so 7 doubled 4 times = 112
I can understand simpler recursion patterns such as adding numbers in a list, or 'power of' functions but I fail to understand how this function 'n_times' evaluates by calling itself ? Can provide an explanation of how this function works ?
I've tagged with scala as I'm taking this course to improve my understanding of scala (along with functional programming) and I think this is a common pattern so may be able to provide advice ?
If n is 0, x is returned.
Otherwise, f (n_times(f , n - 1 , x)) is returned.
What does n_times do? It takes the result of calling f with x, n times, or equivalently: calls f with the result of n_times(f, n - 1, x) (calling f n-1 times on x).
Note by calling f i mean for example:
calling f 3 times: f(f(f(x)))
calling f 2 times: f(f(x))
Just expand by hand. I'm going to call n_times nx to save space.
The core operation is
nx(f, n, x) -> f( nx(f, n-1, x))
terminating with
nx(f, 0, x) -> x
So, of course,
nx(f, 1, x) -> f( nx(f, 0, x) ) -> f( x )
nx(f, 2, x) -> f( nx(f, 1, x) ) -> f( f( x ) )
...
nx(f, n, x) -> f( nx(f,n-1,x) ) -> f( f( ... f( x ) ... ) )
Function n_times has a base case when n = 0 and an inductive case otherwise. You recurse on the inductive case until terminating on the base case.
Here is an illustrative trace:
n_times(double, 4, 7)
~> double (n_times(double, 3, 7)) (* n = 4 > 0, inductive case *)
~> double (double (n_times(double, 2, 7))) (* n = 3 > 0, inductive case *)
~> double (double (double (n_times(double, 1, 7)))) (* n = 2 > 0, inductive case *)
~> double (double (double (double (n_times(double, 0, 7))))) (* n = 1 > 0, inductive case *)
~> double (double (double (double 7))) (* n = 0, base case *)
~> double (double (double 14))
~> double (double 28)
~> double 56
~> 112
It is the same recursion thinking what you know already, just mixed with another concept: higher order functions.
n_times gets a function (f) as a parameter, so n_times is a higher order function, which in turn is capable to apply this f function in his body. In effect that is his job, apply f n times to x.
So how you apply f n times to x? Well, if you applied n-1 times
n_times(f , n - 1 , x)
, then you apply once more.
f (n_times(f , n - 1 , x))
You have to stop the recursion, as usual, that is the n=0 case with x.

RA operator difference - relational algebra

I'm confused how expression is different PROJECT A(r − s) and PROJECT A(r) − PROJECT A(s) is in RDBMS. Can anyone show an example to prove above?
s = [x y]
0 0
1 1
2 2
r = [x y]
0 0
0 1
0 2
1 1
1 2
2 2
A = {y}
PROJECT A(r − s)=[y]
1
2
PROJECT A(r) − PROJECT A(s)=[y]
Here is how to generate (counter)examples like this. The OP problem translates into QBQL assertion:
a <AND> TABLE_DUM = a & r <AND> TABLE_DUM =s <AND> TABLE_DUM
->
a v ( r <AND> <NOT> s ) = (a v r) <AND> <NOT> (a v s).
Where all operations, except "v" (generalized projection) are from D&D Algebra A. The first two implication conditions require relation a to be empty, and relations r and s to have the same header. It outputs:
s = [p]
0
;
r = [p]
1
;
a = [r]
;
*** False Assertion ***
Although this is legitimate answer, some may feel uncomfortable with an idea of projecting relation onto an attribute which doesn't belong to the header. One needs to add one more condition for a and r headers to overlap: (a v s) <AND> TABLE_DUM != TABLE_DUM. Then, the attributes {p,r} should be manually translated into OP's {x,y}. (It is where the bug noticed by Erwin has been introduced).
r = { TUPLE {X 1 , Y 1} }
s = { TUPLE {X 1 , Y 2} }
r MINUS s = r = { TUPLE {X 1 , Y 1} }
Take projections over X (A = {X} ???)
r PROJECT {X} === { TUPLE {X 1} }
s PROJECT {X} === { TUPLE {X 1} }
(r PROJECT {X}) MINUS (s PROJECT {X}) === { }
(r MINUS s) PROJECT {X} === { TUPLE {X 1} }
It's the Y 2 part in s. That's the difference between the tuples in r and s. If you "project away that difference" first, then whatever remains after that is equal, and computing the difference between things that are equal is of course nothing at all.
But if you first compute the difference, then the Y 2 part in the tuple in s causes the tuple in r (sloppily speaking) to not be discarded from the result, and then taking a projection on that result of course produces itself something nonempty.