1 / 56

Declarative prototyping

Declarative prototyping. Declarative prototyping. We present a simple programs development methodology based on mathematical induction, declarative prototyping, procedural design and implementation (the references for this chapter include [Boe84,Mur96,RL99, Som01, Zav89]).

derron
Download Presentation

Declarative prototyping

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Declarative prototyping

  2. Declarative prototyping • We present a simple programs development methodology based on mathematical induction, declarative prototyping, procedural design and implementation (the references for this chapter include [Boe84,Mur96,RL99, Som01, Zav89]). • We use the functional programming language Haskell [PJH99] for declarative prototyping, and C as a language for procedural implementation.

  3. Declarative prototyping • Haskell is a lazy purely functional programming language named after the famous logician Haskell Curry, whose contributions to lambda calculus and combinatory logic are well-known [CF58]. • Classic examples of very high-level languages that can be used for prototyping purposes include: Lisp, Prolog and Smaltalk. • Experiments are reported, e.g., in [Zav89,Mur96].

  4. Declarative prototyping • Haskell is a modern strongly typed functional programming language, appropriate for prototypes development. • Advantages of Haskell as a prototyping tool: • Declarative specifications • Referential transparency (it provides support for equational reasoning) • Polymorphism and higher-order functions • A Haskell specification is typically much shorter than a corresponding C implementation

  5. Declarative prototyping • Haskell programs can be seen as ‘executable mathematics’ [RL99]. • Alternatively, we could adopt Z [Spi92] and develop formal specifications. Z specifications are more abstract, but are not executable. • Haskell prototypes are executable and, therefore, can easily be evaluated and tested. • It is generally accepted that prototyping reduces the number of problems with the requirements specifications [Boe84,Som01]. • The approach considered in this chapter is useful when the problems are novel or difficult.

  6. Declarative prototyping • We present a methodology involving the following steps: • Build a Haskell specification (prototype) The prototype is built by an inductive reasoning, which proves the correctness of the specification. • Design a procedural solution This step involves procedural design decisions, decisions concerning data structures representation, memory allocation policies, etc. • Accomplish the procedural implementation We will use C for procedural implementation.

  7. Declarative prototyping • Mathematical induction is a convenient tool for recursive functions design (for the functions defined on finite structures). • The most common forms of induction are • Induction on natural numbers • Structural induction • They can be treated as instances of a general form of induction, called well-founded induction (see e.g. [Mit96]).

  8. Declarative prototyping • A well-founded relation on a set A is a binary relation  on A with the property that there is no infinite descending sequence a0a1a2 … • A well-founded relation need not be transitive (example: ij if j = i+1, on the natural numbers). • A well-founded relation can not be reflexive (if aa then there is an infinite descending sequence aaa…) • An equivalent definition is that a binary relation  on A is well-founded iff every nonempty subset B of A has a minimal element, where aB is minimal if there is no a’B with a’a.

  9. Declarative prototyping • (Generalized or) Well-founded induction principle • Let  be a well-founded binary relation on set A and let P be some property on A. If P(a) holds whenever we have P(b) for all ba, then P(a) is true for all aA. • More familiar forms of induction can be obtained by using the following well-founded relations: • mn if m+1=n, for natural number induction • ee’ if e is an immediate sub-expression of e’, for structural induction

  10. Declarative prototyping • In the sequel we will use mathematical induction to prove the correctness of recursive definitions. • In each case, we will define a complexity measure: a function that maps the concrete structures in the problem domain to a set equipped with a well-founded relation. • The complexity measure must be chosen so that it decreases upon any recursive call. • We will present various kinds of inductive reasoning.

  11. Declarative prototyping • For simplicity, we do not consider Haskell specifications based on higher-order mappings, and we only give recursive C implementations. • Haskell is polymorphic. C is monomorphic. A Haskell prototype can specify an entire class of C implementations. • For simplicity, we ignore this aspect, and we only consider data structures containing primitive types (numeric values).

  12. Declarative prototyping • Haskell  C transcription • Each Haskell function in the declarative specification is translated to a corresponding C function in the procedural implementation (using auxiliary C functions if necessary). • Haskell functions defined by multiple equations are implemented using conditional statements in C. • For each recursive call in the Haskell specification there is a corresponding recursive call in the C implementation.

  13. Declarative prototyping • Example Set union • Haskell specification: • The specification is correct. This follows by induction on a simple complexity measure: • member(e,xs) - by induction on length(xs) (/ structural induction) • union(xs,ys) – by induction on the length(xs), assuming that xs and ys are lists without duplicated elements member :: (Int,[Int]) -> Bool member (e,[]) = False member (e,x:xs) = if (e == x) then True else member (e,xs) union :: ([Int],[Int]) -> [Int] union ([],ys) = ys union (x:xs,ys) = if member(x,ys) then union(xs,ys) else x:union(xs,ys)

  14. Declarative prototyping • The Haskell prototype behaves as follows (experiments performed using the Hugs interpreter): Main> union([],[1,2,3]) [1,2,3] Main> union([6,7,5,3],[5,6,9,1,2,7]) [3,5,6,9,1,2,7]

  15. Declarative prototyping • Designing the procedural implementation • There are various options: • Recursive implementation • Implementation as WHILE program • Result produced • By the normal function return mechanism • By using an additional parameter transmitted by reference • There are also various options concerning the memory allocation policy • Use static structures (arrays) • Use dynamic structures (lists) • Allocate / not allocate space for the result • Alter / not alter the (input) parameters

  16. Declarative prototyping • We use the following type declaration for the C implementation typedef struct elem { int info; struct elem* next; } ELEM, *LIST;

  17. Declarative prototyping • C implementation of member typedef enum {false,true} BOOL; BOOL member(int e,LIST l) { if (l == 0) return(false); else if (e == l-> info) return(true); else return (member(e,l->next)); }

  18. Declarative prototyping • For union we consider four different implementations: • The first two variants • Alter the input parameters • Do not allocate space for the result. • The last two variants • Do not alter the input parameters • Allocate space for the result

  19. Declarative prototyping LIST union(LIST x,LIST y) { LIST z; if (x == 0) return(y); else if (member(x->info,y)) { z = union(x->next,y); free(x); return(z); } else { z = x; z -> next = union(x->next,y); return(z); } }

  20. Declarative prototyping • The function can be used as follows: LIST x,y,x; … /* Create the ‘sets’ x and y */ … z = union(x,y); /* The ‘set’ z is the union of x and y */

  21. Declarative prototyping • Alternatively, we can implement union as a C function of type void; the function returns its result by using an additional parameter transmitted by reference. void union(LIST x,LIST y,LIST *z) • In the sequel, we find convenient to use the term procedure to refer to such a C function of type void.

  22. Declarative prototyping void union(LIST x,LIST y,LIST *z) { if (x == 0) (*z) = y; else if (member(x->info,y)) { union(x->next,y,z); free(x); } else { (*z) = x; union(x->next,y,&((*z)->next)); } }

  23. Declarative prototyping • The procedure can be used as follows: LIST x,y,x; … /* Create the ‘sets’ x and y */ … union(x,y,&z); /* The ‘set’ z is the union of x and y */

  24. Declarative prototyping • The C function given below allocates space for the result and does not alter the input parameters. LIST union(LIST x,LIST y) { LIST z; if (x == 0) return(copy(y)); else if (member(x->info,y)) { return (union(x->next,y)); } else { z = (LIST)malloc(sizeof(ELEM)); z->info = x->info; z->next = union(x->next,y); return(z); } }

  25. Declarative prototyping • The implementation uses an auxiliary function that makes a physical copy of its parameter. LIST copy (LIST l) { LIST r; if (l == 0) return(0); else { r = (LIST)malloc(sizeof(ELEM)); r->info = l->info; r->next = copy(l->next); return(r); } }

  26. Declarative prototyping • The last implementation solution uses an additional parameter transmitted by reference. It allocates space for the result and does not alter the input parameters. void union(LIST x,LIST y,LIST *z) { if (x == 0) copy(y,z); else if (member(x->info,y)) { union(x->next,y,z); } else { (*z) = (LIST)malloc(sizeof(ELEM)); (*z)->info = x->info; union(x->next,y,&((*z)->next)); } }

  27. Declarative prototyping • In this case we use the following auxiliary procedure to make a physical copy of a list. void copy(LIST l,LIST *r) { if (l == 0) (*r)=0; else { (*r) = (LIST)malloc(sizeof(ELEM)); (*r)->info = l->info; copy(l->next,&((*r)->next)); } }

  28. Declarative prototyping • Example Merging • Haskell specification: • The correctness proof for merge(xs,ys) can proceed by induction on the following computed complexity measure: (length(xs) + length(ys)). The sequences xs and ys are assumed to be ordered. merge :: ([Int],[Int]) -> [Int] merge([],ys) = ys merge(xs,[]) = xs merge(x:xs,y:ys) = if (x<y) then x:merge(xs,y:ys) else y:merge(x:xs,ys)

  29. Declarative prototyping • The Haskell prototype behaves as follows: Main> merge([1,3,5,7],[2,4,6]) [1,2,3,4,5,6,7]

  30. Declarative prototyping • For merge we only design two implementation solutions (as function / procedure). • In the both cases the input parameters are altered and no memory is allocated for the result.

  31. Declarative prototyping • Function LIST merge(LIST x,LIST y) { LIST z; if (x == 0) return(y); else if (y == 0) return(x); else if ((x->info) < (y->info)) { z=x; z->next = merge(x->next,y); return(z); } else { z = y; z->next = merge(x,y->next); return(z); } }

  32. Declarative prototyping • Procedure void merge(LIST x,LIST y,LIST *z) { if (x == 0) (*z)=y; else if (y == 0) (*z)=x; else if ((x->info) < (y->info)) { (*z)=x; merge(x->next,y,&((*z)->next)); } else { (*z)=y; merge(x,y->next, &((*z)->next)); } }

  33. Declarative prototyping • Example Tree flattening using difference lists • Haskell specification: • Difference lists notation: if xs = e1:…:en:ys then xs-ys = [e1,…,en] • The correctness proof for flat(t,ys) can proceed by induction on the structure of t (by structural induction). flat(t,ys) – ys = the list of nodes in t (obtained by a left-node-right inorder traversal) data Tree = Nil | T(Tree,Int,Tree) flat(Nil,ys) = ys flat(T(l,n,r),ys) = flat(l,n:flat(r,ys))

  34. Declarative prototyping • The Haskell prototype behaves as follows: Main> flat(T(T(Nil,2,T(Nil,4,Nil)),1,T(Nil,3,Nil)),[100,100]) [2,4,1,3,100,100]

  35. Declarative prototyping • Apart from the type declaration for lists, in the C implementation we use the following type declaration for trees typedef struct node { int info; struct node *left, *right; } NODE, *TREE; • We offer two implementation solutions.

  36. Declarative prototyping • Function LIST flat(TREE t,LIST y) { LIST x; if (t == 0) return(y); else { x = (LIST)malloc(sizeof(ELEM)); x->info = t->info; x->next = flat(t->right,y); return(flat(t->left,x)); } }

  37. Declarative prototyping • Procedure void flat(TREE t,LIST *x,LIST y) { LIST z; if (t == 0) (*x)=y; else { z = (LIST)malloc(sizeof(ELEM)); z->info = t->info; flat(t->right,&(z->next),y); flat(t->left,x,z); } }

  38. Declarative prototyping Rewriting techniques • Many computations can be described using rewriting techniques. • Sometimes, a data structure must be prepared before performing some calculations or some transformations on it. • We want to transform a binary tree in a list. • We use a rewriting operation to reduce the complexity of the left sub-tree until it becomes Nil. • Next, the transformation is applied recursively on the right sub-tree.

  39. Declarative prototyping • Example Tree flattening using a rewriting transformation • Haskell specification: data Tree = Nil | T(Tree,Int,Tree) deriving Show transf Nil = Nil transf (T(Nil,n,r)) = T(Nil,n,transf(r)) transf (T(T(ll,nl,rl),n,r)) = transf(T(ll,nl,T(rl,n,r)))

  40. Declarative prototyping • The Haskell prototype behaves as follows: Main> transf (T(T(T(Nil,3,Nil),2,Nil),1,Nil)) T(Nil,3,T(Nil,2,T(Nil,1,Nil))) • The result is a degenerate tree (rather than a list)

  41. Declarative prototyping • To prove the correctness of transf we use a more complex measure. • The support set is NN, and we use the so-called lexicographic ordering (that we denote here by ) over NN. The lexicographic ordering is defined as follows: (n1,m1)  (n2,m2) if (n1<n2) or (n1=n2 and m1<m2) • It is easy to check that  is a well founded relation over NN. Also, for each (n,m)NN either (n=0, m=0) or (0,0)(n,m).

  42. Declarative prototyping • The correctness of transf can be proved by induction on the following composed complexity measure: c:Tree  NN u,v:Tree  N c(t)=(u(t),v(t)) for any t :: Tree • Here, u(t) is the number of nodes in t and v(t) is a measure of the complexity of the left sub-tree: u(Nil) = 0 u(T(l,n,r)) = 1 + u(l) + u(r) v(Nil) = 0 v(T(l,n,r)) = 1+v(l) • Remark that c(t)=(0,0) iff t=Nil. • We present two different implementation solutions.

  43. Declarative prototyping • Function TREE transf(TREE t) { TREE p; if (t == 0) return(0); else if (t->left == 0){ t->right = transf(t->right); return(t); } else { p = t; t = p->left; p->left = t->right; t->right = p; return(transf(t)); } }

  44. Declarative prototyping • Procedure with inout parameter void transf(TREE *t) { TREE p; if ((*t) != 0) { if (((*t)->left) == 0) transf(&((*t)->right)); else { p = (*t); (*t) = p->left; p->left = (*t)->right; (*t)->right = p; transf(t); } } }

  45. Declarative prototyping • Example Mutual recursion and simultaneous induction • Haskell specification: data Btree = NilB | B(Int,Ttree,Ttree) data Ttree = NilT | T(Int,Btree,Btree,Btree) flatB :: (Btree,[Int]) -> [Int] flatB (NilB,ys) = ys flatB (B(n,tl,tr),ys) = n:flatT(tl,flatT(tr,ys)) flatT :: (Ttree,[Int]) -> [Int] flatT (NilT,ys) = ys flatT (T(n,bl,bm,br),ys) = n:flatB(bl,flatB(bm,flatB(br,ys)))

  46. Declarative prototyping • Let t :: Ttree; b :: Btree t = T(2,NilB,B(3,NilT,T(4,NilB,NilB,NilB)),NilB) B = B(1,t,T(5,B(6,NilT,NilT),NilB,B(7,NilT,NilT))) • The Haskell prototype behaves as follows: Main> flatT (t,[0,0,0,0]) [2,3,4,0,0,0,0] Main> flatB (b,[]) [1,2,3,4,5,6,7]

  47. Declarative prototyping • Claim • flatB(b,ys)-ys = the list of nodes in b (obtained by a node-left-right traversal) • flatT(t,ys)-ys = the list of nodes in t (obtained by a node-left-mid-right traversal) • ProofBy simultaneous induction on the number of nodes in the tree structure (the first parameter of each function): • Base case For trees with zero nodes the specification is: flatB(NilB,ys)=ys, flatB(NilT,ys)=ys; this is correct since ys-ys=[]. The both functions behave correctly for trees with zero nodes. • Induction step For the induction step each function uses the induction hypothesis of the other function.

  48. Declarative prototyping • For the procedural implementation we use the following type declarations: typedef struct Bnode { int info; struct Tnode *l, *r; } BNODE, *BTREE; typedef struct Tnode { int info; struct Bnode *l,*m,*r; } TNODE, *TTREE; • We give implementations as functions and procedures.

  49. Declarative prototyping • A pair of functions LIST flatT(TTREE,LIST); LIST flatB(BTREE b,LIST y) { LIST x; if (b == 0) return(y); else { x = (LIST)malloc(sizeof(ELEM)); x->info = b->info; x->next = flatT(b->l,flatT(b->r,y)); return(x); } }

  50. Declarative prototyping LIST flatT(TTREE t,LIST y) { LIST x; if (t == 0) return(y); else { x = (LIST)malloc(sizeof(ELEM)); x->info = t->info; x->next = flatB(t->l,flatB(t->m,flatB(t->r,y)); return(x); } }

More Related