RU beehive logo ITEC dept promo banner
ITEC 380
2009spring
ibarland

homeinfolecturesexamshwsarchive

lect12
prolog

; Lists:
;   scheme      Prolog
;    empty         []
;    cons           .
;    list          [ ..,..,..]


; For example:
mem_v2(_,[]) :- not(true).
mem_v2(D,.(D,_)) :- true.
mem_v2(D,.(_,R)) :- mem(D,R).


% We can simplify this syntax,
% once we internalize Prolog's mind-set:

mem(X,[X|_]).
mem(X,[_|R]) :- mem(X,R).






% Other functions to write:
% atLast(X,L)
% removeLast(L1,L2)
% reverse(L,R)
% append (L1,L2,L)
% zip(L1,L2,L)
%   e.g.  zip( [a,b,c], [d,e,f], [a,d,b,e,c,f] ).

Numbers in Prolog

To do arithmetic (rather than pattern-matching), we use a keyword: “is”:

Var is Expr
However, there is a caveat: all variables in Expr have to already have been bound, at the time when this constraint is evaluated! Here are some examples:

    % Write: mem
    
    
    % Write: len (length of a list) (remind yourself of scheme version):
    %
    % len(Data,N) : true if `Data` contains `N` items. 
    len([],0).
    len([_|R],X) :- len(R,Y), X is Y+1.
    
    
    % A note on numbers:
    %
    coolerThan(X,Y) :- X > Y.
    %
    % prolog has no problem with coolerThan(4,7).
    % But what about coolerThan(4,X) --
    % What do you expect the answer should be?
    
    %%% arithmetic is weird in Prolog:
    %  Try writing the predicate
    %   biggerByOne(X,Y) if X-1 = Y.
    %
    %  One attempt would be:  "biggerByOne(X,X-1)."
    % Alas, this won't do: to do arithmetic in prolog,
    % you have to use the keyword "is":
    % 
    biggerByOne(X,Y) :- Y is X-1.
    %
    % This is pretty good:  As you'd expect:
    biggerByOne(5,4).
    not(biggerByOne(5,6)).
    %   biggerByOne(3,X)  will give solution "X=2", like you'd expect.
    % HOWEVER:
    %   biggerByOne(Y,2)  will won't give "Y=3"; it gives an error!
    % When you use 'is', every variable on the right-hand-side 
    % *has to already have* a value.
    
    % Here's some motivation, as to why this should be the case:
    % 
    % f(X,Y) : true when Y = g(X),
    %                  where g(X)=x^5 - 101x^4 -10x^3 +9x - 909
    %  (Note how we turn a numeric function g : R->R
    %  into a boolean predicate   f : RxR -> boolean)
    %
    f(X,Y) :- Y is X*X*X*X*X - 101*X*X*X*X - 10*X*X*X + 1010*X*X + 9*X - 909.
    %
    % What if we query f(X,0) -- this means
    % prolog has to be smart enough to handle 5th-degree polynomials.
    % While you can imagine prolog designers lookup up the quartic formula
    % and supporting it in the language,
    % it turns out there is *no* closed-form solution for degree 5 and higher (!!)
    %  [This is a surprising result of math -- Galois.]
    % NB I happened to construct f so that it has nice roots though:
    %    f = (x-1)(x+1)(x-3)(x+3)(x-101)
    
    
    
    % Define factorial in Prolog:
    %   (define (! n)
    %      (if (zero? n)
    %          1
    %          (* n (! (sub1 n)))))
    %
    % Here's what we want to say:
    %
    %   fact(0,1).
    %   fact(N,NF) := NF = N*Z, fact(N-1,Z).
    
    % However, it gives an error! -- 
    % prolog sees "NF = N*Z",
    % and it's expecting too much to try to find *all* values of NF,Z
    % which would make this true.
    % Even writing "NF is N*Z" doesn't help, since Z isn't instantiated.
    % Instead, we'll have alter our spec, so that Z gets 
    % instantiated before we mention N*Z.
    
    fact(N,NF) :- Nless1 is N-1,
                  fact(Nless1,Z), 
                  NF is N*Z.
    fact(0,1).
    
    
    % Let's try it.
    % Uh-oh, infinite loop!  Why?
    % Solution: swap the order.
    %
    % Now it seems better.
    % But what does fact(3,7) do?  What about fact(3,N), if we reject 6?
    %
    % Solution: add "N>1" to the recursive case.
    % Why does it fix Q2?
    %
    % We see that (esp with numbers), prolog isn't as purely declarative
    % as we'd like.  Instead, we're forced to think about how it implements
    % its matching engine :-(  Arguably, that's a shortcoming of Prolog.
    % (Note that Mathematica goes to great lengths to handle numbers,
    %  although it's more functional than declarative.
    %
    %
nth(0,[F|_],F).
nth(I,[_|R],Answer) :-
  Iless1 is I-1,
  nth(Iless1,R,Answer).

sorting in Prolog

     %%%%%%%%%%%%%%
     % Sorting:
     % We'll just provide the 'spec':
     % L2 is a sorted version of L1 if:
     %  - L1 is a permutation (re-arrangement) of L2, and
     %  - L2 is in ascending order (er, non-decreasing).
     
     scrapSort(L1,L2) :- permutation(L1,L2), nondecreasing(L2).
      
     % Here're the helper functions:
     
     nondecreasing([]).
     nondecreasing([_]).
     nondecreasing([F,S|R]) :- F=<S, nondecreasing([S|R]).
     
     
     badPerm1([],[]).
     badPerm1([F|R],Y) :- append2(Y1,[F|Y2],Y), 
                          append2(Y1,Y2,PY), 
                          badPerm1(R,PY).
     % This is a correct *specification* of permutation.
     % However, it has several drawbacks:
     % (a) written non-intuitively,
     % (b) using 'append' is a sledgehammer (poor performance too),
     % (c) it only half-works, in Prolog:
     %    badPerm1([3,4,5],[5,3,4]) is true
     % but
     %    badPerm1([3,4,5],X) gives only *one* answer
     %    and then loops forever.
     % However, it works 'in reverse':
     %    badPerm1(X,[3,4,5]) gives all solutions.
     
     badPerm2([],[]).
     badPerm2([F|R],Y) :- remove(F,Y,Z), badPerm2(Z,R).
     %
     % This is also a correct spec,
     % and it fixes drawbacks (a) and (b), 
     % but still suffers from (c).
     % In fact, it's worse: badPerm2 no longer
     % works in reverse -- badPerm2(X,[3,4,5]) gives
     % *two* (?!) solutions, and then backtracks
     % into la-la land.
     
     % Here's a fully-working version:
     % like badPerm2 except that it destructures the *return* value.
     %
     permutation([],[]).
     permutation(X,[F|R]) :- remove(F,X,Z), permutation(Z,R).
     
     remove(F,[F|L1],L1).
     remove(F,[G|L1],[G|L2]) :- remove(F,L1,L2).
     
     
     
     %% Now, back to sorting...
     % Reflecting on scrapSort:
     %   Consider the *order* of solutions
     %   permutation([3,4,5],X) and permutation([5,4,3],X).
     % How many permutations does scrapSort([3,4,5],X) look at?
     % How about scrapSort ([5,4,3],X) look at?
     %
     % Now, consider this problem amplified:
     %     scrapSort([8,7,6,5,4,3,2,1],X) runs okay. But
     % But scrapSort([10,9,8,7,6,5,4,3,2,1],X) takes a while.
     %
     % The name "scrapSort" was chosen because its performance
     % is a piece of scrap: O(n!).
     
     
     
     
     
     
     
     % Ironically, 'quicksort' is more straightforward than 'scrapsort',
     % even though we specify *how* to sort rather than *what sorted means*.
     %
     
     
     %  QuickSort
     %  Author: Varesano Fabio
     %  http://www.varesano.net/blog/fabio/quick+sort+implemented+prolog
     
     /* [+,-] */
     quicksort([], []).
     quicksort([HEAD | TAIL], SORTED) :- partition(HEAD, TAIL, LEFT, RIGHT),
                                         quicksort(LEFT, SORTEDL),
                                         quicksort(RIGHT, SORTEDR),
                                         append(SORTEDL, [HEAD | SORTEDR], SORTED).
     
     /* [+,+,-,-] */
     partition(PIVOT, [], [], []).
     partition(PIVOT, [HEAD | TAIL], [HEAD | LEFT], RIGHT) :- HEAD @=< PIVOT,
                                                              partition(PIVOT, TAIL, LEFT, RIGHT).
     partition(PIVOT, [HEAD | TAIL], LEFT, [HEAD | RIGHT]) :- HEAD @> PIVOT,
                                                              partition(PIVOT, TAIL, LEFT, RIGHT).
     
     /* [+,+,-] */
     append([], LIST, LIST).
     append([HEAD | LIST1], LIST2, [HEAD | LIST3]) :- append(LIST1, LIST2, LIST3).
     
     
     
     
     
     
     
     % Here is a another, more verbose version
     % (the version I started writing myself ... 
     % Note that I'm used to partitioning into three sections,
     % so that we always *reduce* the size of a partition
     % (even if our pivot is the maximum value).
     % Study the above sol'n, to see how the Prolog
     % makes sure we always recur on smaller lists (even
     % when the pivot is the maximum value *and* when
     % it's the minimum value).
     
     qsort([],[]).
     qsort([F|R],L2) :- partition([F|R],F,Sm,Eq,Lr), 
                        qsort(Sm,SmSorted), 
     		   qsort(Lr,LrSorted), 
     	           append3(SmSorted,Eq,LrSorted,L2).
     
     partition([],_,[],[],[]).
     partition([F|R],Pivot,Smaller,Equal,Larger) :- 
       F<Pivot,
       Smaller = [F|SmallerRest],
       partition(R,Pivot,SmallerRest,Equal,Larger).
     partition([F|R],Pivot,Smaller,Equal,Larger) :- 
       F=Pivot, 
       Equal = [F|EqualRest],
       partition(R,Pivot,Smaller,EqualRest,Larger).
     partition([F|R],Pivot,Smaller,Equal,Larger) :- 
       F>Pivot, 
       Larger = [F|LargerRest],
       partition(R,Pivot,Smaller,Equal,LargerRest).
     
     
     append3(L1,L2,L3,All) :- append2(L1,L2,L1and2), append2(L1and2,L3,All).
     append2([],L2,L2).
     append2([F|R],L2,[F|AllRest]) :- append2(R,L2,AllRest).
     
     
     
     
     /*****************
      Reflections/thoughts on prolog:
       * The idea of declarative programming is cool:
         Just by specifying what *constitutes* a correct answer,
         you can write a program (and often it's efficient).
       + Sometimes though, it's too inefficient (scrapSort).
       + Worse though: to really write Prolog, you need to
         thoroughly understand *how* it implements searching
         (badPerm).
     
       + Seeing how different 'cultures' compute is mind-expanding;
       * Pattern matching: Way cool!  
         This is something *any* language can use
         (ML, Haskell; Scheme has a 'match' library).
       * The idea of specifying correctness of an answer
         (separately from an algorithm)
         makes me a better [Java, whatever] programmer:
         often having a clean spec of correctness
         leads directly to clean, correct code.
      *****************/

homeinfolecturesexamshwsarchive


©2009, Ian Barland, Radford University
Last modified 2009.Apr.27 (Mon)
Please mail any suggestions
(incl. typos, broken links)
to iba�rlandrad�ford.edu
Powered by PLT Scheme