[isabelle-dev] functions over abstract data

Christian Sternagel c.sternagel at gmail.com
Fri Aug 23 10:06:24 CEST 2013


Dear Alex and all,

once again I started to transform a rather adhoc parser datatype (in 
IsaFoR) into some (I think) nicer variant using abstract datatypes with 
invariants. After tinkering around for several days (where everything 
worked out nicely) I hit a wall, and also remembered that I already did 
so some years ago, when I wanted to do the same thing. Only this time, 
instead of giving up, I started to think about destructing the wall (not 
with my head though ... well ... in a way ;)).

Let me first state my "problem" (parsers are only a special instance). 
Suppose you have an abstract datatype with invariant like

   typedef T = "{x. Inv x}"

with an "Abs"/"Rep" pair and the invariant "Inv".

---------------------------------------------------------
*Example:* Parsers:

   ('a, 'b) raw_parser = 'a list => string + ('b * 'a list)

   leq p <-> (ALL ts x ts'.
     p ts = Inr (x, ts') --> length ts' <= length ts)

   typedef ('a, 'b) parser = "{p::('a, 'b) raw_parser. leq p}"
     morphisms run Parser
---------------------------------------------------------

When defining a function "f", whose result type is "T", it might be 
necessary to "peek under the hood" in order to show termination. When 
doing this manually, we destroy the abstraction and always have to 
reason about the raw-level and even worse, also all the existing 
constants with result type T have to be deconstructed in the definition.

---------------------------------------------------------
*Example:* Suppose we already have do-notation for parsers
(i.e., bind), monadic return, op <|> (for alternatives),
and a parser "just" that parses just the given string. One
simple parser that could be constructed is:

   par =
     (do { just "(", par; just ")"; return () }) <|>
     return ()

However, without lifting the abstraction, we have no handle
on termination. Resulting in something like

   par_aux ts = run
     ((do { just "(", Parser par_aux; just ")"; return () }) <|>
       return ())
     ts

   par = Parser par_aux

where I have to unfold all the abstract definitions in the
body of "par_aux" in order to prove termination and the
termination proof gets really messy (in fact I did not
succeed at all.)
---------------------------------------------------------

Here comes my suggestion (until now I only did a manual --
i.e., all proofs and auxiliary definitions by hand --
case-study for the "par" parser, to check feasibility).

(This only makes sense when "Rep" returns something
of function type.)

Function specifications may be given parameters "Abs", "Rep",
"Inv" (most likely corresponding to some abstract datatype
with invariant) with lemmas:

   Abs_inverse: "Inv x ==> Rep (Abs x) = x"
   Rep_eqI: "(!!x. Rep f x = Rep g x) ==> f = g"

Then when the user writes something like

   function (abstract Abs Rep Inv)
     f :: "T"
   where
     "f = F f"

this is internally replaced by

   "Rep f x = Rep (F f) x"

for the inductive definition of the function graph G_f, we replace the 
existing (see Alex' thesis, page 17) introduction rules (for simplicity 
just using a single recursive call):

   (Gamma ==> (r[h/f], h(r[h/f]) : G_f) ==> (x, F h x) : G_f

by

   (Gamma ==> (r[h/f], Rep h (r[h/f])) : G_f ==> (x, Rep (F h) x) : G_f

(So we more or less consider "Rep f" instead of just "f" to be the newly
defined function for the purpose of internal constructions.)

The introduction rules for the domain are modified similarly. The 
internal definition of the function is now two-fold:

   "f' = (%x. THE y. par_graph x y)"
   "f = Abs f'"

Furthermore, an additional lemma (besides the usual "x : dom_f ==> EX!y. 
(x, y) : G_f") is needed:

   "Inv f' ==> x : dom_f ==> (x, Rep (F f) x) : G_f"

(which is proved similarly to the existence part of the standard lemma 
and where "Inv f'" is needed to obtain, together with "Abs_inverse", 
"Rep f = f'".) Furthermore the usual psimps get an additional assumption:

   psimps: "Inv f' ==> x : dom_f ==> Rep f x = Rep (F f) x"

Then the termination proof involves two steps:
   - show "!!x. x : dom_f", and
   - show "(!!x. x : dom_f) ==> inv f'"
(which is both trivial for the "par" parser). Then we can derive "Rep f 
x = Rep (F f) x", which together with "Rep_eqI" yields the desired "f = 
F f".

Remark: ideally this should also handle additional parameters of "f" 
(which are outside of the abstraction). Maybe something like:

   "f x = F f x"

turned into

   "Rep (f x) y = Rep (F f x) y"

with

   "f' = (%x y. THE z. ((x, y), z) : G_f"
   "f = (%x. Abs (f' x))

Any comments? Would anybody else find this useful?

cheers

chris

PS: I started to dive into the function package. My first hurdle is that 
for "f" not of function type (as in "par"), no recursive calls are 
generated, since "Function_Ctx_Tree.mk_tree" says that "No cong rules 
could be found".


More information about the isabelle-dev mailing list