Lecture 21 - Thu Nov 11 Notes by Dale Miller ----------------------- Consider the following simple example of the generate-and-test approach to programming. The following predicate succeeds for only natural numbers. nat z. nat (s X) :- nat X. We can also define multiplication as repeated addition. For example, mult z N z. mult (s N) M P :- mult N M Q, plus M Q P. We can now use these predicates together with the clauses defining Fibonacci numbers with the query: ?- nat N, fib N F, mult N N F. This query generates all numbers (the first atom in this query), then computes the N'th Fibonacci number F, and then checks that F is the product of N with N. This query will succeed exactly three times. Can you find them? By the way, integers are actually built into lambda Prolog. For example, below are some of the operations needed to do integer computations. builtin comparisons: <, >, >=, =< builtin operations: +, -, *, div, mod, quot, rem, min, max The comparisons are binary predicates that succeed or fail depending on what it is that is being tested. For example, ?- 3 =< 4. succeeds since 3 is less-than-or-equal to 4. The operations on integers are only available within the "is" predicate. This is a special predicate of the form ?- Variable is Expression. where Variable is a variable and Expression is built using integers and the builtin operations listed above. Thus the query, ?- X = 4, Y is (4 * X) + 9. yeilds X = 4 and Y = 25. For another example, the length predicate (which relates a list to the integer that is its length), can be defined as length nil 0. length (X::L) N :- length L M, N is M + 1. To see a longer piece of code, consider the following clauses: _______________________________________________________________ psort L1 L2 :- permutation L1 L2, inorder L2. permutation nil nil. permutation L (H::T) :- append V (H::U) L, append V U W, permutation W T. inorder nil. inorder (A::nil). inorder (A::B::Rest) :- A =< B, inorder (B::Rest). qsort nil nil. qsort (First::Rest) Sorted :- split First Rest Small Big, qsort Small SmallSorted, qsort Big BigSorted, append SmallSorted (First::BigSorted) Sorted. split X nil nil nil. split X (A::Rest) (A::S) B) :- A =< X, split X Rest S B. split X (A::Rest) S (A::B)) :- A > X, split X Rest S B. _______________________________________________________________ psort is a (very inefficient) search procedure that permutes a list until it generates one that is sorted. qsort (quick sort) is more efficient. All constants in lambda Prolog must be given types: there is no type inference (except for locally bound variables). For example, the predicates above have the foll type permutation list S -> list S -> o. type psort list int -> list int -> o. type inorder list int -> o. type qsort list int -> list int -> o. type split int -> list int -> list int -> list int -> o. The type of formulas is o. Thus, all these symbols produce formulas when they are applied tot eh appropriate 2, 3, or 4 arguments. Lambda Prolog allows for higher-order programming, like that found in SML. It is possible to define all the following predicates. Notice each one takes a predicate as an argument (this is the higher-order aspect). type mappred (A -> B -> o) -> list A -> list B -> o. type forevery, forsome (A -> o) -> list A -> o. type sublist (A -> o) -> list A -> list A -> o. mappred P nil nil. mappred P (X :: L) (Y :: K) :- P X Y, mappred P L K. forevery P nil. forevery P (X :: L) :- P X, forevery P L. forsome P (X :: L) :- P X ; forsome P L. sublist P (X::L) (X::K) :- P X, sublist P L K. sublist P (X::L) K :- sublist P L K. sublist P nil nil. All code in lambda Prolog is organized into modules. There are several things that are put into modules, but we will focus here only on three things: the first line is declares the name of the module, the type declarations appear prior to the place where the constants declared are used, and then there are the clauses. For example, assume that the following lines are in the file small.mod. _______________________________________________________________ module small. type append list A -> list A -> list A -> o. append nil K K. append (X :: L) K (X :: M) :- append L K M. type permutation list S -> list S -> o. type psort list int -> list int -> o. type inorder list int -> o. type qsort list int -> list int -> o. type split int -> list int -> list int -> list int -> o. % A very expensive sort using generate-and-test psort L1 L2 :- permutation L1 L2, inorder L2. permutation nil nil. permutation L (H::T) :- append V (H::U) L, append V U W, permutation W T. inorder nil. inorder (A::nil). inorder (A::B::Rest) :- A =< B, inorder (B::Rest). % A better sorting predicate qsort nil nil. qsort (First::Rest) Sorted :- split First Rest Small Big, qsort Small SmallSorted, qsort Big BigSorted, append SmallSorted (First::BigSorted) Sorted. split X nil nil nil. split X (A::Rest) (A::S) B :- A =< X, split X Rest S B. split X (A::Rest) S (A::B) :- A > X, split X Rest S B. _______________________________________________________________ Noitice that comments are allowed following a %. If we go to unix, we can run terzo, load this module, and query against it, as is illustrated below: _______________________________________________________________ Unix 3> setenv TERZO_BASE "/home/users2/sml/terzo/" Unix 4> terzo GC #0.0.0.0.1.1: (0 ms) Terzo lambda-Prolog, Version 1.2b, Built Tue Feb 9 18:18:13 1999 Terzo> #load "small.mod". [reading file small.mod] module small [closed file small.mod] Terzo> #query small. ?- permutation (1::2::3::nil) L. L = 1 :: 2 :: 3 :: nil ; L = 1 :: 3 :: 2 :: nil ; L = 2 :: 1 :: 3 :: nil ; L = 2 :: 3 :: 1 :: nil ; L = 3 :: 1 :: 2 :: nil ; GC #0.0.0.0.3.25: (10 ms) L = 3 :: 2 :: 1 :: nil ; no more solutions ?- _______________________________________________________________ Notice that the garbage collector prints out GC messages sometimes. Besides higher-order programming, it is also possible to use higher-order types within data structures. These are used to help encode bound variables within data structures. For example, consider encoding the untyped lambda-calculus. We introduce one new type, tm, and constructors for application and abstraction. kind ty type. type abs (tm -> tm) -> tm. type app tm -> tm -> tm. The following untyped terms would be represented as data structures in lambda Prolog: lambda x . x (abs x \ x) lambda x . (x x) (abs x \ (app x x)) lambda f lambda x . f (f x) (abs f \ abs x \ (app f (app f x))) .... The clauses for representing call-by-value evaluation would be written as type eval tm -> tm -> o. eval (M N) V :- eval M (abs R), eval N U, eval (R U) V. eval (abs R) (abs R). The rules for simple typing can be written as kind ty type. type arrow ty -> ty -> ty. type typeof tm -> ty -> o. typeof (app M N) A :- typeof M (arrow B A), typeof N B. typeof (abs R) (arrow A B) :- pi x\ (typeof x A => typeof (R x) B). The "pi x\" is the syntax in lambda Prolog for "forall x" at the meta-level. The symbol "=>" denotes implication at the meta language. The second clause thus states that "if for every x where you assume that x has type A, then (R x) has type B, then we can conclude that (abs R) has type (arrow A B).