Module 17 Lab
-
As in last week’s lab, you should open two terminal windows, create a directory for your Prolog work and
cd
to it in both windows, and create a file calledlab17.pl
. -
Lists are almost as important in Prolog as they are in Haskell, and there is a similar syntax for defining them. The empty list is designated
[]
. The list whose first element isX
and remaining elements are the listXs
is designated[X|Xs]
. Note thatX
is a single element andXs
is a list, just like in Haskell. So the notation[X|Xs]
in Prolog is equivalent toX:Xs
in Haskell (except that in Haskell these variables would have to be lowercase). A list can also be made by just putting its elements between brackets separated by commas. Execute the query[1,2,3,4]=[X|Xs].
inswipl
. What is the result? -
Prolog has lots of built-in list predicates. Type
member(X,[1,2,3,4]).
Hit semicolon until the goal fails. Typemember(1,[X|Xs]).
and hit semicolon several times before just hitting Enter. What is happening? -
One difference between Haskell and Prolog with regard to list syntax is that the concatenation operator can be used multiple times in Haskell, so
a:b:c:[]
is the list[a,b,c]
. In Prolog, this would be[a,b,c|[]]
instead (or just[a,b,c]
, of course). But pattern matching with the concatenation operator in Prolog is just as powerful as it is in Haskell; for example[X1,X2,X3,X4|Xs]
matches a list with at least four elements;X1
is instantiated to the first,X2
to second, and so forth, andXs
is instantiated to the rest of the list after the fourth element. -
Lets make a predicate
size(L,N)
that counts how many (N
) elements are in a listL
. We know that the size of an empty list is zero:size([], 0).
We also know that the size of a non-empty list is one more than the size of that list with its first element removed:size([_|Xs], N) :- size(Xs, XsN), N is XsN + 1.
Note that the special infix predicateis
must be used to get Prolog to evaluate an arithmetic expression. Note thatXsN
here could be considered a “temporary variable” that is used to denote an intermediate result useful for computing our desired results. Type these rules intolab17.pl
, save the file, and consult it. Experiment to see how these rules work. Write tests for thesize
predicate. -
Note that the order in which things occur here is very important: if the second rule has its conjuncts reversed, there will be an instantiation error because
XsN
will not have a value whenXsN+1
is evaluated. Furthermore, one must keep in mind the order in which the inference engine tries to satisfy its goals. Notice too that we are using recursion in this predicate; we will use recursion to process lists frequently. Finally, Prolog has a built-in predicatelength
that works likesize
. -
Write rules in
lab17.pl
to implement a predicateend(L, E)
that places the last element of listL
inE
(in other words, the predicateend(L, E)
should be true whenE
is the last element ofL
). It fails for the empty list. There is a built-in predicate calledfail
(no arguments) that you can use in a rule to obtain this behavior. Also write tests for this predicate. Note: Prolog has a built-inlast
predicate that works likeend
. -
Write rules to implement the predicate
max(L, M)
whose first argument is a list and whose last argument is the maximum number in the list. The predicate should fail if the list is empty. There are also built-in numeric in-fix comparison operators<
and=<
for less than and less than or equal to. You may assume that the list contains only numbers. Write tests for this predicate. Prolog has the predicatesmin_list
,max_list
,sum_list
, andsort
built-in. -
Write rules to implement the predicate
evens(L, M)
where the first argument is a list of numbers and the second argument is all of the even numbers from the first list (in the same order that they appear in the original list). For example, the following should succeed:evens([1,2,3,4], [2,4]).
Note that in Prolog, the predicateX is Y mod Z
to assert the result of a modular arithmetic operation. You will probably need to use the cut operator (!
) to stop this predicate from returning multiple answers when you query it using a variable. -
Write rules to implement the predicate
concatenate(L1, L2, L)
whereL
is the result of appendingL1
andL2
. (Hint: look back at the solution formyConcat
from the Module 9 lab in Haskell for inspiration regarding the general approach.) Write tests for it. Prolog also has a built-inappend
predicate that you can use in the project if you don’t get this one working. -
Prolog is more relaxed about types than Haskell. In Haskell, a list must contain elements that are all the same type. In Prolog, a list can contain elements of any type, including other lists. Hence the list
[1,a,[b,[]],4]
is perfectly fine in Prolog. It helps to be able to distinguish the types of list elements. The built-in predicatelist(X)
does this, succeeding ifX
is a list and failing otherwise. -
Suppose that we wanted to represent binary trees like we did in the second Haskell lab. We can do this using lists as tuples because Prolog allows us to have multiple types in a list. For instance, we could designate
[empty]
to represent an empty tree and[node,X,L,R]
to represent a node with some valueX
and subtreesL
andR
. Here are Prolog rules that will generate binary search trees using that kind of data structure:% top-level: invoke recursive insertion on an empty tree bst(List, Tree) :- build_bst(List, [empty], Tree), !. % empty list: no tree modifications necessary build_bst([], Tree, Tree). % non-empty list: insert the head and recurse on the tail build_bst([X|Xs], CurrentTree, NewTree) :- insert_bst(X, CurrentTree, TmpTree), build_bst(Xs, TmpTree, NewTree). % empty tree: create new node for value insert_bst(V, [empty], [node,V,[empty],[empty]]). % binary node: insert value into left or right subtree (ignore if equal) insert_bst(V, [node,V,L,R], [node,V,L,R]). insert_bst(V, [node,X,L,R], [node,X,NewL,R]) :- V < X, insert_bst(V, L, NewL). insert_bst(V, [node,X,L,R], [node,X,L,NewR]) :- V > X, insert_bst(V, R, NewR).
Test this code by comparing the results of running
bst([1,2,3], T).
andbst([3,2,1], T).
Ifswipl
abbreviates the output (e.g.,[node|...]
), you can append; true
to the query to cause it to pause after the solution, and then you can press thew
(write) key to show the full solution. -
Write a rule
find(Val, Tree)
that is true only ifVal
is an element in the binary search treeTree
. You can use the following rule to simplify testing:find_bst(Val, List) :- bst(List, Tree), find(Val, Tree), !.
For instance,
find_bst(3, [1,3,5]).
should succeed whilefind_bst(4, [1,3,5]).
should fail. Note that there is no notion of “returning true” or “returning false” in Prolog; predicates may only either succeed or fail! Thus, you do not in general need to describe the “false” conditions (except in the empty-tree base case), only the true conditions. -
Write a rule
sorted(Tree, List)
that creates a sorted listList
of all items in a binary search treeTree
. You can use the following rule to simplify testing:sorted_bst(List, SortedList) :- bst(List, Tree), sorted(Tree, SortedList), !.
For instance,
sorted_bst([3,8,6,1,4,9],X).
should produce the resultX = [1,3,4,6,8,9]
. Note that if the original list contains more than one of a particular element, only one will appear in the sorted list because the binary search tree does not store duplicates. -
Write a rule
count_nodes(Tree, Count)
that counts the number of nodesCount
in a binary search treeTree
.count_bst(List, Count) :- bst(List, Tree), count_nodes(Tree, Count), !.
For instance,
count_bst([3,6,8],X)
should produce the resultX = 3
. Note that if the original list contains duplicates, they will not be counted.Congratulations! You have just written an extremely complicated and much slower alternative implementation for the
length
andsort
built-in predicates! :)However, you now also have all the tools you need to implement the project for this module. Good luck!
This lab was originally written by Dr. Chris Fox and expanded by Dr. Mike Lam.