GRIPS Jocelyn Paine, May 1987 Updated January 1991 Introduction. ============= It has been said that Prolog is the Fortran of logic-programming. Teaching Prolog to beginners raises many difficulties: the cut; the need to allocate variables to pass data from call to call; the lack of syntactic distinction between predicates, functions, and commands with side-effects; the tedium of building lists; to name a few. In one respect, Prolog is perhaps even closer to machine-code than Fortran (making the doubtful assumption that Fortran is higher-level than machine-code). When writing a Prolog program, things which are conceptually function calls must be rewritten relationally. This requires you to invent extra variables to pass the output of one relation into the input of the next. In addition, relations, unlike function calls, can't be nested; so you have to unwind sequences such as f(g(h(x))) into h(x,V1), g(V1,V2), f(V2,Result). This is very similar to the way that machine-code (and some versions of Fortran) force you to compile expressions into sequences of calls and temporary register allocations. And though that might have been necessary in the 1950s, it ought not to be so now. Another defect of Prolog is the lack of cues for the reader about what a predicate is doing. Is something conceptually a function, where all but one argument will be inputs and the final one an output? Is it a general relation, with many outputs for one input? Or is it a command, being called for its side-effects? The GRIPS pre-processor allows you to write Prolog clauses as function definitions which it translates into Prolog clauses. The right-hand side of a definition is an expression, consisting of (possibly nested) calls to other functions. GRIPS allocates result variables and unwinds calls as described above. You can also write definitions as predicates (e.g. Prolog), or as commands (to make it clear that they are being written for their side-effects). GRIPS also provides a top-level which reads and evaluates functional expressions. This is similar to the Prolog top-level except that the input is an expression to be evaluated, not a statement to be proven true or false. The top-level interpreter. ========================== A term T will be treated as follows: T=halt : Exit from GRIPS (as for Prolog 'halt'). T=prolog: Restore the Prolog TLI. With the command 'grips' from Prolog, this gives a way to switch between the two. T=pr(G) : Evaluate G as a Prolog goal, display its result using the standard Prolog top-level interpreter, and then return to GRIPS. T=test(G) : Translate G as if it were a GRIPS guard, then display its result using the standard Prolog interpreter, and then return to GRIPS. T=do(G) : Translate G as if it were a GRIPS guard, then call it and return to GRIPS, writing 'Done' if it succeeds, else 'Failed'. The difference between 'do' and 'test' is that 'do' is for commands whereas 'test' is for guards. T=echo(E): As below, but GRIPS displays the result of translating E into Prolog. This is mainly for debugging. T=: Translate T as a GRIPS expression, evaluate it, and display the result, then return to GRIPS. If T fails during evaluation, display 'Failed' and return to GRIPS. Examples of top-level interpreter. ================================== G> halt. - Exits from GRIPS. G> pr( X=1+2 ). - Displays 'X=1+2' and then the 'More (y/n)' message, before returning to GRIPS. The 1+2 is left unevaluated, as in Prolog. G> test( X=1+2 ). - Displays 'X=3' and then the 'More (y/n)' message, before returning to GRIPS. The 1+2 has been evaluated, because it's in a guard. G> 1+2. - Displays 'Result=3'. G> fred(4). - Displays 'Failed' (assuming that a call of fred(4,_) would fail. How to consult files. ===================== There are two predicates, 'grips_consult(F)' and 'grips_reconsult(F)'. Each takes a filename as argument. These predicates imitate consult and reconsult. The file being read can contain the following: 1. GRIPS definitions - these will be translated into Prolog and then asserted. If you're reconsulting, any definitions with the same functor and arity will be deleted. 2. GRIPS directives - these will be translated into Prolog and called. 3. Prolog directives - these will be called. 4. Prolog definitions- these will be asserted. If you're reconsulting, old definitions will be deleted. 5. Grammar rules - these will be translated into Prolog and treated as above. GRIPS in 'consult' mode. ======================== When GRIPS is consulting a file, it recognises the following types of term. The first six are definitions to be translated into clauses and asserted; the other five are to be evaluated immediately. Head <- Expression. Head does Command. Head if Guard. Head :- Tail. Head --> Tail. Head. grips(Expression). do( Guard ). test( Guard ). ?- Goal. :- Goal. Function definitions. ===================== If the main functor is <-, this defines a function. Functions are translated into predicates with an extra argument on the right. The right-hand side of the function is translated as described under Expressions, into a goal and a result variable. This result variable becomes the extra argument of the predicate. If the right-hand side contains a guard, that guard is translated as described under Guards. The functions are made deterministic - GRIPS inserts a cut after each translated condition, or (if none), after the final tail goal. Note that the forms f <- a if b. f <- b => a. f <- a if b else c if d else e if f. f <- a if b else c if d else e. are actually definitions where the right-hand side is a conditional expression. Examples of function definitions. ================================= double( N ) <- N*2. to double( N, V ) :- V is N*2, !. quadruple( N ) <- double( double(N) ). to quadruple( N, V ) <- double( N, V` ), double( V`, V ), !. factorial(N) <- 1 if N =< 0. factorial(N) <- N * factorial(N-1) if N > 0. to factorial(N, 1) :- N =< 0, !. factorial(N, V) :- N > 0 , !, V` is N - 1 , factorial(V`, V``), V is N * V``. count( L ) <- 0 if L = []. count( L ) <- 1 + count( tail(L) ) if L \= []. to count([], 0) :- !. count(L, V) :- L \= [] , ! , tail(L, V`) , count(V`, V``), V is 1 + V``. Here, I've used V, V`, V``... to indicate new Prolog variables introduced by GRIPS. Note that GRIPS may optimise some of these translations. Commands definitions. ===================== If the main functor is 'does', this defines a command. Commands are translated into predicates with the same number of arguments. GRIPS translates the actions in the command body as if they were guards, and places them after the translated guards. The commands are made deterministic - GRIPS inserts a cut between the conditions and actions, or (if no conditions), after the final tail goal. Examples of command definitions. ================================ converse does write_list(reply_to(readline('?'))) and converse. to converse :- readline(?, V) , reply_to(V, V`) , write_list(V`), converse, !. Immediate mode evaluation. ========================== Just as, with Prolog, ?-G causes G to be called, so, in GRIPS, grips(E) invokes the GRIPS interpreter on E. This is only likely to be useful if you're calling GRIPS from inside an integrated editor and you want to display results. More useful are do(G) and test(G). If these occur where a definition is expected, then G will be translated into Prolog, and called. They are thus the GRIPS analogue of ?-G. You can also use the Prolog directives ?-G and :-G. These are obeyed immediately, but G is not translated first. Predicates. =========== A definition like A if B0 is translated into A :- B1 where B0 is treated as a guard and translated into B1. The difference between these predicate definitions and Prolog ones is that arguments in the guards are treated as function calls, and not left alone. Examples of predicate definitions. ================================== divides_by_4( N ) if ( N/2 rem 2 ) = 0. to divides_by_4(V) :- V` is _1 / 2 rem 2, V` = 0. Prolog definitions. =================== If the main functor is anything else, the definition is left alone, and compiled by Prolog. Thus doit :- glorb(S), bletch(S,U), V is 6.5*U, write(V). ?- op( 100, xfx, *** ). :- consult( bat ). sentence --> np, vp. are left alone. The second and third invoke goals: the fourth is treated as a grammar rule. Expressions. ============ Any expression E is translated into a goal G and a result variable V, such that when G is called, it instantiates V. Numbers, variables, and atoms (including []) are translated into the goal 'true', and a result which is the thing itself. Any expression q(E) is translated into the goal 'true' and the result E. This is a way of protecting things that you don't want evaluated. An expression eval(A) where A is an atom, is translated into the goal A(V) and the result variable V. That is, it calls the one-argument goal whose functor is A. This provides a way of calling parameterless functions. Since such "functions" are likely only to be of use for their side-effects, this use won't be frequent. Any other expression eval(E) is translated as E. Any expression test(G) do(G) is translated to a goal formed by translating G as a guard. The result variable is bound to the atom 'true'. Thus test( X=1+2 ). goes to GOALS: _1 is 1 + 2 RESULT VAR: true Result = true. Any expression pr(G) is translated to the goal G. The result variable is bound to 'true'. This is as for 'test' and 'do' except that G is left alone, not treated as a guard. Thus: pr( X=1+2 ). goes to GOALS: _1 = 1 + 2 RESULT VAR: true Result = true. This is one way of getting Prolog goals into GRIPS untranslated. Lists are translated by making a new list, each of whose elements will be the result of evaluating the corresponding original element. Thus: [ a, b, 1+2, double(4) ] . goes to GOALS: _1 is 1 + 2 , double(4, _2) RESULT VAR: [a, b, _1, _2] Result = [a, b, 3, 8]. An expression of the form A++B is treated as list concatenation. Each list is translated separately, and a goal is then generated in which they're combined by append. ++ has the same precedence and associativity as +. An expression of the form (E where G) is translated by translating G as a guard, and then translating E. G should instantiate something in E. Thus: [ 1 + V ] where V=4/5 . [ 1 + (V where V=4/5) ]. both go to GOALS: _1 is 4 / 5 , _2 is 1 + _1 RESULT VAR: _2 Result = [1.8]. An expression of the form (E if G) (G => E) is a conditional expression. It fails if G fails, otherwise it returns result E. It is translated by translating G as a guard, and then translating E. Note that if it occurs at the outer level of a function body, then failure of G will cause GRIPS to try the following clause. The expression forms A if B else ... are extended conditionals. If B succeeds they return A; otherwise they go on to the thing after the else. This can be another if..else. You can put B=>A instead of A if B. An expression of the form (all E where G) returns a list of all E's that satisfy G. Structures of other kinds are translated into function calls. How depends on whether they are evaluable (in Prolog) by 'is'. If so, then an 'is' goal will be built to evaluate as much as possible, with subsidiary goals to evaluate some arguments. Otherwise, just subsidiary goals will be built, which add an output argument to the structure: Thus: 1 + 3 goes to GOALS: _1 is 1 + 3 RESULT VAR: _1 Result = 4. 1 + 3 * sin(3) goes to GOALS: _1 is 1 + 3 * sin(3) RESULT VAR: _1 Result = 1.15701. 1 + 3 + double(3) goes to GOALS: double(3, _1) , _2 is 1 + 3 * _1 RESULT VAR: _2 Result = 19. factorial(1+3*double(3))+4). goes to GOALS: double(3, _1) , _2 is 1 + 3 * _1 , factorial(_2, _3) , _4 is _3 + 4 RESULT VAR: _4 Result = 121645100408832004. Notice that interior structures are also evaluated, and not left as structures. Guards. ======= These are GRIPS conditions, which GRIPS translates into Prolog goals. Any guard pr(G) where G is an arbitrary term, is left as G. This is a way of getting untranslated Prolog into GRIPS. Any guard do(G) test(G) means the same as G, and is translated by translating G. This is pointless, except that it's analogous to the way 'test' behaves with expressions. The guard 'nothing' is translated to true. This allows you to write the command definition: c(A) does nothing if A=1. A guard that is a variable at translation-time is left alone. Any guard (A and B) (A,B) is translated into (A',B'), where A' and B' are the translations of A and B as guards. Any guard (A or B) (A;B) is translated likewise into (A';B'). Any guard (A foreach C) is translated into foreach(C,A), which does A for as many times as C can be resatisfied, and succeeds after all resatisfactions of C. C and A must be guards; A should contain commands. Any guard not(A) call(A) is translated into not(A') or call(A'), where A' and B' are the translations of A and B as guards. Any guard (A=>B) (B if A) is translated into (A',!,B'), where A' and B' are the translations of A and B as guards. Any guard else(A,B) is translated into (A';B'). This gives the guard equivalent of the conditional expressions. Guards P(A), where P is assert, asserta, retract, or retractall, are left alone. This ensures that A is treated as a term to be used with the database, and not an expression to be evaluated. A guard of the form phrase(G,L) is assumed to be a call to the parsing predicate phrase/2. The first argument G (which will be a non-terminal symbol) is left alone. The second argument (which will be a list to be evaluated) is translated as an expression. Other structures are translated by treating their arguments as GRIPS expressions, and recursively translating them. This process can be optimised for arithmetic operators like < and =:=, which partly evaluate their arguments even in Prolog; and for =. User-callable Prolog predicates. ================================ The predicates below are exported: grips. grips_consult. grips_reconsult.