Here is a trivial translator from language foo to bar:
type_synonym vname = "string"
type_synonym 'a env = "vname ⇒ 'a option"
datatype foo_exp = FooBConst bool
datatype foo_type = FooBType | FooIType | FooSType
datatype bar_exp = BarBConst bool
datatype bar_type = BarBType | BarIType
fun foo_to_bar_type :: "foo_type ⇒ bar_type option" where
"foo_to_bar_type FooBType = Some BarBType" |
"foo_to_bar_type FooIType = Some BarIType" |
"foo_to_bar_type _ = None"
inductive foo_to_bar :: "foo_type env ⇒ foo_exp ⇒ bar_type env ⇒ bar_exp ⇒ bool" where
"Γ⇩B = map_comp foo_to_bar_type Γ⇩F ⟹
foo_to_bar Γ⇩F (FooBConst c) Γ⇩B (BarBConst c)"
code_pred [show_modes] foo_to_bar .
values "{t. foo_to_bar Map.empty (FooBConst True) Map.empty t}"
The last line causes the following error:
Wellsortedness error
(in code equation foo_to_bar_i_i_i_o ?x ?xa ?xb ≡
Predicate.bind (Predicate.single (?x, ?xa, ?xb))
(λ(Γ⇩F_, aa, Γ⇩B_).
case aa of
FooBConst c_ ⇒
Predicate.bind (eq_i_i Γ⇩B_ (foo_to_bar_type ∘⇩m Γ⇩F_))
(λ(). Predicate.single (BarBConst c_))),
with dependency "Pure.dummy_pattern" -> "foo_to_bar_i_i_i_o"):
Type char list ⇒ bar_type option not of sort equal
No type arity list :: enum
Could you suggest me how to fix it?
Also foo_to_bar has mode i => i => o => o => boolpos. How should I execute values to generate both 3rd and 4th arguments?
In general I'd advise against using inductive to define something that can easily be expressed as a function. While the predicate compiler comes with a lot of bells and whistles to make computational sense of an inductive definition, there are a lot of problems that can arise because it's so complex. In your case, the problem lies in the line
Γ⇩B = map_comp foo_to_bar_type Γ⇩F
You are trying to compare two functions. The predicate compiler doesn't know that it can be seen as a "defining equation". In a sense, you're asking the predicate compiler to solve an impossible problem.
Your life will be much easier if foo_to_bar is defined as a function (or plain definition) instead. It'll work out of the box with the code generator.
Again a small example with unexpected results.
theory Scratch
imports Main
begin
datatype test = aa | bb | plus test test
axiomatization where
testIdemo : "x == plus x x"
lemma test1 : "y == plus y y"
Now i get the following messages:
Auto solve_direct: The current goal can be solved directly with
Scratch.testIdemo: ?x ≡ test.plus ?x ?x
Auto Quickcheck found a counterexample:
y = aa
Evaluated terms:
test.plus y y = test.plus aa aa
and when i try to run sledgehammer i get:
"remote_vampire": Try this: using testIdemo by auto (0.0 ms).
"spass": The prover derived "False" from "test.distinct(5)" and "testIdemo".
This could be due to inconsistent axioms (including "sorry"s) or to a bug in Sledgehammer.
If the problem persists, please contact the Isabelle developers.
Is this due to me messing with ==?
Or do i need to set some other sort of restriction for my axioms?
Follow up:
Apparently i should not play with equals :P
So i need to define my own relation.
axiomatization
testEQ :: "test ⇒ test ⇒ bool" (infixl "=" 1)
where
reflexive [intro]: "x = x" and
substitution [elim]: "x = y ⟹ B x = B y" and
symmetric : "x = y ⟹ y = x"
So i guess i have to define my basic properties. reflexiveness, substitution and symmetry seem nice for a start. I could make it generic with 'a => 'a => bool
now i would go on to define more of my relation. to stay with the example:
axiomatization
plus :: "test⇒ test⇒ test" (infixl "+" 35)
where
commutative: "x + y = y + x" and
idemo: "x + x = x"
a) Is this so far correct
b) How to proceed from here
So far i don't think this would replace subterms out of lemmas which is kinda the point of equivalence.
Your axiom implies e.g. aa = plus aa aa, which is false, because constructors of a datatype are always distinct, by construction. (cf. thm test.distinct)
In fact, if you use axiomatization, you should really know what you're doing – it's very easy to introduce inconsistencies that way. (obviously)
If you want to have a type with certain properties, you have to construct it. For instance, you could define a representation type of your type (e.g. as a datatype), then define some equality relation on it (i.e. what values should be equal to what other values) and then define the ‘real’ type as the quotient type of your representation type over that relation.
Update 2 (151015)
I put some source below. It shows a skeleton of what I may use.
With some help, I'm getting more sophisticated. I now know the difference between a numeral type type, and a constant of type numeral. The notation is all the same, and I'm used to using functions that operate on terms, not types. With CARD, that changes the paradigm some.
As far as numeral type notation, even with show_consts, it's not obvious that I'm looking at a type or a term. So, I draw off always using show_sorts and show_consts. A 0-ary type constructor type, like nat, never gets annotated with anything. Knowing that helps.
I said that a certain theorem wasn't being proved by magic without not importing Numeral_Type, but that's not true.
Succinct syntax is important, so getting good type inference is important. It looks like I get good type inference when using the numeral type.
From the answer, I also got my first use of using a dummy type, which, at this point, appears to be a much better way to do things.
Here's some source:
theory i151013c_numeral_has_type_enforcement
imports Complex_Main "~~/src/HOL/Library/Numeral_Type"
begin
declare [[show_sorts, show_consts]]
datatype ('a,'size) vD = vC "'a list"
definition CARD_foo :: "('a,'s::{finite,card_UNIV}) vD => ('a,'s) vD => nat"
where
"CARD_foo x y = card (UNIV :: 's set)"
notation (input) vC ("vC|_" [1000] 1000)
notation (output) vC ("vC|_" [1000] 999)
value "CARD_foo (vC|x::(nat,32) vD) vC|y = (32::nat)" (*True*)
value "CARD_foo (vC|x::(nat,65536) vD) vC|y = 65536" (*True*)
type_synonym nv3 = "(nat, 3) vD"
notation CARD_foo (infixl "*+*" 65)
value "vC|m *+* (vC|n::nv3) = 3" (*True*)
type_notation (input) vD ("< _ , _ >")
term "x::<'a,'s::{finite,card_UNIV}>"
term "vC|m *+* (vC|n::<'a,64>) = 64"
value "vC|m *+* (vC|n::<'a,64>) = 64" (*True*)
(*Next, Am I adding 2 types of type numeral? Or am I adding 2 constants of
type numeral, which produces a numeral type? The notation for numeral types
and numeral type constants is identical.*)
value "vC|[3] *+* (vC|y::<nat,2 + 3>)"
term "vC|[3] *+* (vC|y::<nat,2 + 3>)"
(*I guess 2 and 3 are types. In the output panel, 0-ary types, such as 'nat',
don't get annotated with anything, where nat constants do.*)
lemma "vC|[3] *+* (vC|y::<nat,2 + 3>) = 5"
by(simp add: CARD_foo_def)
(*Type error clash. Oh well. Just don't do that.*)
term "(vC|x::<'a,5>) *+* (vC|y::<'a,2 + 3>)"
definition vCARD :: "('a, 's::{finite,card_UNIV}) vD => nat" where
"vCARD x = CARD('s)"
declare vCARD_def [simp add]
lemma
"vCARD(x::<'a,'s::{finite,card_UNIV}>) =
vCARD(y::<'b,'s::{finite,card_UNIV}>)"
by(simp)
end
Update (151014)
Here I explain to M.Eberl why I don't use the numeral type, based upon what I know and have experienced.
Related comment about typedef and a past question
A while back, I got exposed to ~~/src/HOL/Library/Cardinality, along with Numeral_Type from this answer by M.Eberl:
Trying to generalize a bit vector that uses typedef, bool list, and nat length
That question was also related to typedef. Partly from my experiments at that time, I try to stay away from typedef, and use the magic that comes with datatype.
Even today, I started having problems with the sz8 typedef not working with value, because of abstraction problems. After looking back at the answer linked to above, it partially shows what has to be done to get typedef working with value. I have a size8 in the new source I include that shows what I did. I think there's a problem with the equal function, that it needs to be fixed similar to what's shown in the answer above.
An Example size datatype and vector datatype
Now, I make a few comments about the two example datatypes in the second source I include below.
The use case for a size type is for vector length, where the vectors are lists.
The size type enforces that for a binary operation, two vectors have the same length. I then only have to check that the two vectors actually are the right length.
The problem with the numeral type is that there's no type enforcement. My example function has the following signature, with the datatype shown:
datatype ('a,'size) vD = vC "'a list" 'size
CARD_foo :: "(nat,'s::card_UNIV) vD => (nat,'s) vD => nat"
But I can to this:
term "CARD_foo (vC [] 10) (vC [] 11)"
Other comments are in the source. I have a typedef size8 at the end, and I don't know, at this time, how to fix that.
Though there's no type enforcement with the numeral type, I guess I can depend on type size using CARD, based on this:
theorem CARD_of_type_of_terms_of_same_type_are_equal:
"CARD_foo (vC n size1term) = CARD_foo (vC m size2term)"
unfolding CARD_foo_def
by(auto simp add: CARD_foo_def)
To get that by magic, I had to not import "~~/src/HOL/Library/Numeral_Type".
Thanks for the help. It's invaluable, and thanks for how to get the proofs I asked for originally. It helps to learn things here and there about typedef.
The new example source:
theory i151013b_2nd
imports Complex_Main (*"$GEZ/e/IsE"*)
"~~/src/HOL/Library/Cardinality" "~~/src/HOL/Library/Numeral_Type"
begin
declare [[show_sorts, show_consts]]
(*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*)
(*::¦ NUMERAL TYPE DOESN'T GUARANTEE TYPE ENFORCEMENT FOR A BINARY OP ¦:::*)
(*----------------------------*)
(*The size type as a datatype.*)
datatype sz8D = sz8C bool bool bool
(*-----------------------------------*)
(*Typdef 'n <= 7 would be preferable.*)
lemma UNIV_sz8D:
"UNIV =
{sz8C False False False, sz8C False False True, sz8C False True False,
sz8C False True True, sz8C True False False, sz8C True False True,
sz8C True True False, sz8C True True True}"
by(auto, metis (full_types) sz8D.exhaust)
lemma card_UNIV_sz8D [simp]: "card (UNIV :: sz8D set) = 8"
by(unfold UNIV_sz8D, auto)
instantiation sz8D :: card_UNIV
begin
definition "finite_UNIV = Phantom(sz8D) True"
definition "card_UNIV = Phantom(sz8D) 8"
instance
apply(default)
unfolding UNIV_sz8D finite_UNIV_sz8D_def card_UNIV_sz8D_def
by(auto)
end
(*-----------------------------------------*)
(*The vector type with an example function.*)
datatype ('a,'size) vD = vC "'a list" 'size
definition CARD_foo :: "(nat,'s::card_UNIV) vD => (nat,'s) vD => nat" where
"CARD_foo x y = card (UNIV :: 's set)"
thm CARD_foo_def
(*--------------------------------------------------------*)
(*sz8D: Size enforcement. Error if I mix other size types.*)
value "CARD_foo (vC [] (s::sz8D)) (vC [1] (t::sz8D))" (*outputs 8*)
value "CARD_foo (vC [] (sz8C False False False)) (vC [1] (t::sz8D))"
(*-------------------------------------*)
(*numeral: No enforcement of size type.*)
term "CARD_foo (vC [] 10) (vC [] 11)" (*
"CARD_foo (vC [] (10::'a::{card_UNIV,numeral}))
(vC [] (11::'a::{card_UNIV,numeral}))" :: "nat" *)
(*Can't eval the type to nat, even if they're the same*)
value "CARD_foo (vC [] 10) (vC [] 10)"
(*But here, CARDs are different anyway; no enforcement, no value.*)
value "CARD_foo (vC [] 10) (vC [] 11)" (*
"of_phantom card_UNIV_class.card_UNIV" :: "nat"*)
lemma "CARD_foo (vC [] 10) (vC [] 11) = z" oops (*
show_consts:
CARD_foo (vC [] (10::'a)) (vC [] (11::'a)) = z
'a :: {card_UNIV,numeral} *)
(*Can evaluate when there's not a conflict.*)
term "CARD(10)"
value "CARD(10)" (*outputs 10*)
lemma "CARD(10) = 10" by simp (*show_consts: 'UNIV :: 10 set'*)
value "CARD(11)" (*outputs 11*)
(*No eval if CARD('a) is used in a function.*)
definition fooID :: "'a::card_UNIV => nat" where
"fooID x = CARD('a)"
term "fooID(5)"
value "fooID(5)"
(*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*)
(*::¦ HOWEVER, BY FUNCTION UNIQUENESS, I SUPPOSE THERE'S NO AMBIGUITY ¦:::*)
(*[>) I have to drop down to only 'src/HOL/Library/Cardinality' to get this.
[>) For some reason, it won't unfold the definition.*)
theorem CARD_of_type_of_terms_of_same_type_are_equal:
"CARD_foo (vC n size_1term) = CARD_foo (vC m size_2term)"
unfolding CARD_foo_def
by(auto simp add: CARD_foo_def)
(*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*)
(*::¦ CAN'T USE TYPEDEF AFTERALL IF I CAN'T FIX THIS ¦::::::::::::::::::::*)
(*NOTE ABOUT PLUG'N'PLAY:
[>) See http://stackoverflow.com/q/27415275
[>) 'value' for 'CARD_foo' needs class 'equal'
[>) '[code abstract]' didn't work, so I used '[code]'.*)
typedef size8 = "{n::nat. n ≤ 7}"
morphisms size8_to_nat Abs_size8
by(blast)
definition nat_to_size8 :: "nat => size8" where
"nat_to_size8 n == if n ≤ 7 then Abs_size8 n else Abs_size8 0"
lemma nat_to_size8_code [code]:
"size8_to_nat (nat_to_size8 n) = (if n ≤ 7 then n else 0)"
unfolding nat_to_size8_def
by(simp add: Abs_size8_inverse)
setup_lifting type_definition_size8
instantiation size8 :: equal
begin
lift_definition equal_size8 :: "size8 => size8 => bool" is "λx y. x = y" .
instance
by(default, transfer, auto simp add: equal_size8_def)
end
instantiation size8 :: card_UNIV
begin
definition "finite_UNIV = Phantom(size8) True"
definition "card_UNIV = Phantom(size8) 8"
instance sorry
end
value "CARD_foo (vC [] (Abs_size8 0)) (vC [] (Abs_size8 0))" (*
Abstraction violation: constant Abs_size8 *)
end
Original Question
I'm using typedef to define some types that are used as size types, to be used like this: CARD(sz8). I can use datatype, but it takes a lot longer for it to set itself up.
I guess I don't understand how to show two values are unique with the inverse theorems generated by typedef for sz8.
I have my type, sz8, and I instantiate it as card_UNIV. What's incomplete is my theorem card_UNIV_sz8, which is "card (UNIV::sz8 set) = 8".
theory i151013a
imports Complex_Main "~~/src/HOL/Library/Cardinality"
"$GEZ/e/IsE"
begin
declare [[show_sorts, show_consts]]
typedef sz8 = "{n::nat. n ≤ 7}"
by(blast)
theorem UNIV_sz8:
"UNIV = {s::sz8. ∃n. n ≤ 7 ∧ s = Abs_sz8 n}"
using Rep_sz8 Rep_sz8_inverse
by(fastforce)
theorem foo1:
assumes "Abs_sz8 n ∈ {s::sz8. ∃n ≤ 7. s = Abs_sz8 n}"
shows "n ∈ {n. n ≤ 7}"
proof
fix n :: nat
note assms
obtain n1 where
f1: "n1 ≤ (7::nat) ∧ Abs_sz8 n = Abs_sz8 n1"
using Rep_sz8 Rep_sz8_inverse
by(fastforce)
hence "n = n1"
oops
find_theorems name: "sz8"
instance sz8 :: finite
apply default
unfolding UNIV_sz8
by(simp)
theorem card_UNIV_sz8 [simp]:
"card (UNIV::sz8 set) = 8"
unfolding UNIV_sz8
sorry
instantiation sz8 :: card_UNIV
begin
definition "finite_UNIV = Phantom(sz8) True"
definition "card_UNIV = Phantom(sz8) 8"
instance
apply default
unfolding finite_UNIV_sz8_def card_UNIV_sz8_def
by(simp_all)
end
end
The answer to your question
First of all: I will answer your question, but then I will tell you why what you are doing is unnecessary.
You can show the distinctness of the values using the theorem sz8.Abs_sz8_inject, which shows up if you do find_theorems Abs_sz8:
(?x::nat) ∈ {n::nat. n ≤ (7::nat)} ⟹
(?y::nat) ∈ {n::nat. n ≤ (7::nat)} ⟹
(Abs_sz8 ?x = Abs_sz8 ?y) = (?x = ?y)
You can prove your theorem e.g. like this:
lemma sz8_image: "x ∈ Abs_sz8 ` {0..7}"
by (cases x rule: sz8.Abs_sz8_cases) auto
theorem card_UNIV_sz8 [simp]: "card (UNIV::sz8 set) = 8"
proof -
from sz8_image have "UNIV = Abs_sz8 ` {0..7}" by blast
also from sz8.Abs_sz8_inject have "card … = card {0..(7::nat)}"
by (intro card_image inj_onI) simp_all
finally show ?thesis by simp
qed
What you should do instead
Have a look at the theory ~~/src/HOL/Library/Numeral_Type, where ~~ stands for the Isabelle root directory.
This defines a type n for every positive integer n, which contains exactly the numbers from 0 to n - 1 and even defines lots of typeclass instances and modular arithmetic on them. For example:
value "(2 - 5 :: 10) = 7"
> "True" :: "bool"
This is probably exactly what you want and it comes fully set up; doing all of this by hand is quite tedious, and if you ever need a size 16 type, you have to do the same thing all over again.
Update: More on numeral types
In your updated question, you claim that type checking for numeral types does not work. That is not correct; the problem is merely that the 10 in your vC [] 10 has no meaning. Your intention was probably to specify that the length parameter 'size in the type of that function must be 10.
However, every numeral type contains a 10. For instance, (10 :: 5) = 0 and (10 :: 6) = 4. Therefore, the 10 and 11 in there do not cause any type restrictions at all.
What you have to do is constrain 'size at the type level:
datatype ('a,'size) vD = vC "'a list"
consts CARD_foo :: "(nat,'s::card_UNIV) vD => (nat,'s) vD => nat"
term "CARD_foo (vC [] :: (nat, 10) vD) (vC [] :: (nat, 11) vD)"
(* Type error *)
If you really want to do something on the value-level similar to what you tried to do, you can use the following trick:
datatype ('a,'size) vD = vC "'a list" "'size itself"
consts CARD_foo :: "(nat,'s::card_UNIV) vD => (nat,'s) vD => nat"
term "CARD_foo (vC [] TYPE(10)) (vC [] TYPE(11))"
'a itself is basically a singleton type that contains the value TYPE('a). I think the variant without these itself values is probably more convenient in the long run though.
As for why your CARD_of_type_of_terms_of_same_type_are_equal does not work, I cannot say without seeing the definition of the constants involved, I am quite sure that everything that works with your hand-crafted sz8 type will work with numeral types.
At the end of the day, you can always replace sz8 everywhere in your code with 8 and everything should still work.
I was looking at the case operator, to see what it can do for me.
There's not a problem. Given the examples I'm working with, I accept that "it does what it does", but I ask some questions in case there's something more to learn.
It appears that the case operator can't take arguments that are constants unless they're datatype constants. If they aren't, it gives the uninformative message: "Error in case expression: type mismatch".
Can I get case to pattern match on a non-datatype constant?
The keyword simps_of_case sometimes produces simp rules from a case, and sometimes it doesn't.
Is there something I should know about the example below where it just reproduces the yield2_def as the simp rule?
I took one example for simps_of_case from How to define a partial function in Isabelle?. It seems that I learned somewhere that case is designed around datatype, but I don't find where I learned about that.
I include a short theory with the examples:
theory i150903a__a0
imports Complex_Main "~~/src/HOL/Library/Simps_Case_Conv"
begin
(*************************************************************************)
section{* simps_of_case: Doesn't generate any new simps *}
(*(58)/src/HOL/Lazy_Sequence.thy
[∙) Doesn't generate any new simp rules. Because of 'list_of_lazy_sequence'?*)
definition yield2 :: "'a lazy_sequence => ('a × 'a lazy_sequence) option"
where
"yield2 xq = (case list_of_lazy_sequence xq of
[] => None
| x # xs => Some (x, lazy_sequence_of_list xs))"
thm yield2_def
find_theorems name: yield2
simps_of_case yield2_simps[simp]: yield2_def
thm yield2_simps
find_theorems name: yield2
(*************************************************************************)
section{* simps_of_case: Does generate new simps *}
(*140813a__SOz__How to define a partial function in Isabelle*)
partial_function (tailrec) oddity :: "nat => nat" where
"oddity x = (case x of (Suc (Suc n)) => n | 0 => 0 )"
thm oddity.simps
find_theorems name: oddity
simps_of_case oddity_simps[simp]: oddity.simps
thm oddity_simps
find_theorems name: oddity
(*************************************************************************)
section{* Case constant arguments must be datatypes? *}
declare[[show_sorts]]
(*Works*)
term "case (x,y) of (None, None) => (0::'a::zero, 0::'b::zero)"
term "case (x,y) of (0::nat, 0::nat) => (0::'a::zero, 0::'b::zero)"
term "case (x,y) of (0::nat, x # xs) => (0::'a::zero, 0::'b::zero)"
term "case (x,y) of (a,b) => (0::'a::zero, 0::'b::zero)"
fun foofun :: "('a::zero, 'b::zero) prod => ('a, 'b) prod" where
"foofun (x,y) = (case (x,y) of (a,b) => (0,0))"
(*OUTPUT: "Error in case expression: type mismatch"*)
term "case (x,y) of (0::nat, 0::int) => (0::'a::zero, 0::'b::zero)"
fun foofun :: "('a::zero, 'b::zero) prod => ('a, 'b) prod" where
"foofun (x,y) = (case (x,y) of (0,0) => (0,0))"
(*************************************************************************)
section{* Theory end *}
end
Case syntax is only sugar – it gets desugared to a nested application of the appropriate "case combinators". The datatype manual hints at that briefly in §2.3. As far as I know, there must be a case combinator for a given type in order for case syntax to work. Existing case combinators are e.g. case_option and case_list. Now, if something has not been defined as a datatype, there are other ways to obtain that combinator (e.g. the free_constructors) command. The int doesn't have that kind of setup.
simps_of_case transforms an equation of the form f x = P (case x of ...) into equations of the form f pat1 = P ..., f pat2 = P ..., ... If you're pattern matching on a term which is not a variable from the argument list, it can't do that. In your example, what did you expect to get generated?
I'm using the mathematical toolkit in HOL-Z to discharge some Isabelle predicates. specifically I'm using the partial function definition to define some of the relations in a Z specification that I'm writing, where I convert the schema's to Specification statements so that I can generate simple HOL predicates.
definitions from HOL-Z toolkit
type_synonym ('a,'b) lts = "('a*'b) set" (infixr "<=>" 20)
prodZ ::"['a set,'b set] => ('a <=> 'b) " ("_ %x _" [81,80] 80)
"a %x b" == "a <*> b"
rel ::"['a set, 'b set] => ('a <=> 'b) set" ("_ <--> _" [54,53] 53)
rel_def : "A <--> B == Pow (A %x B)"
partial_func ::"['a set,'b set] => ('a <=> 'b) set" ("_ -|-> _" [54,53] 53)
partial_func_def : "S -|-> R ==
{f. f:(S <--> R) & (! x y1 y2. (x,y1):f & (x,y2):f --> (y1=y2))}"
rel_appl :: "['a<=>'b,'a] => 'b" ("_ %^ _" [90,91] 90)
rel_appl_def : "R %^ x == (#y. (x,y) : R)"
When I write the following within a predicate:
FORALL x. balance %^ x = Bbalance %^ x
where balance and Bbalance are both partial functions(in Z), of the form ('a <=> 'b) in Isabelle, I assume it behaves fine.
How can I define another function where I say that the two partial functions are totally disjoint for all 'x' . I mean the relational application of the same value on two partial functions 'balance' and 'Bbalance' NEVER produce the same value. something like...
FORALL x. balance %^ x \noteq Bbalance %^ x
sorry for the poor explanation. we learn through expert advice :).
Your rel_appl_def rule makes use of the epsilon function. According to the Stanford Encyclopedia of Philosophy (SEP)(*) in his Hamburg lecture in 1921 (1922), Hilbert first presented the idea of using choice functions to deal with the principle of the excluded middle in a formal system for arithmetic.
The governing axiom of the epsilon function reads as follows:
(A x) --> (A (# A))
In classical logic, because of ex falso quodlibet, if (A x) fails, (# A) can take any interpretation. This means that your rel_appl_def rule gives any value when you supply an argument x that is not in the domain dom R.
So probably what you want to use as equality would be the following boolean function (^) on two partial functions:
f ^ g = (dom f = dom g) & (!x. x : dom f --> f %^ x = g %^ x)
What I cannot understand when SEP writes, the second, of perhaps greater current interest, is the use of the epsilon-operator in the theorem-proving systems HOL and Isabelle, where the expressive power of epsilon-terms yields significant practical advantages.
I have seen a much simpler treatment of partial functions in practice, namely using the option type. So a partial function f belongs simply to a type A => B option. But when you cannot change the types in your project, it is probably wiser to seek the equality that fits your requirements, the above definition could be a candidate.
Bye
(*)
The Epsilon Calculus, Jeremy Avigad and Richard Zach
First published Fri May 3, 2002; substantive revision Wed Nov 27, 2013
http://plato.stanford.edu/entries/epsilon-calculus/