250 likes | 269 Views
This lecture discusses lists as a data structure for non-numeric programming. Lists in Prolog are represented as a sequence of terms, including nested lists. The lecture also compares list types in Haskell and Prolog, and demonstrates various operations and programming techniques with lists.
E N D
Formal Models of ComputationPart IIThe Logic Model Lecture 5 – Lists
Lists • Data structure for non-numeric programming. • A sequence of any number of items. • The items can be any Prolog term (including nested lists!). • Useful when we don’t know in advance how many items we are going to have. • Flexible and generic. • In Prolog, a list is represented as a sequence of terms, separated by commas and within “[…]”: [123,hello,Var1,p(a,X),[1,2,3]] formal models of computation
Types in lists: Haskell vs Prolog • In Haskell, for every type t there is a type [t] for lists whose elements are of type t • In Haskell, every list must have type [t] (for some t), and hence all its elements must be of the same type • So you can’t for instance have a list, some of whose elements have type int and some of whose elements have type [int], e.g. [1,[2,3],4] • Prolog places no restrictions on what elements of lists can be • Less restrictive, but you lose the advantages of type checking formal models of computation
Lists . 1 . 2 . 3 [] • Lists are ordinary Prolog terms. • The notation […] is just a shorthand: [1,2,3] .(1,.(2,.(3,[]))) • Lists are thus represented internally as trees: • N.B.: the last element is always the empty list [] formal models of computation
Lists . 1 . 2 . 3 [] • Lists are recursively defined as: • The empty list []; or • A term followed by a list. • Lists can enter queries: ?- List = [1,2,3]. List = [1,2,3] yes ?- formal models of computation
Lists (Cont’d) • Prolog provides a notation for manipulating lists. • The notation is [H|T], a shortand for .(H,T) • H stands for the first element of the list. • T stands for the remaining elements of the list and must be a list. • We can build and decompose lists with [H|T]: ?- List = [1,2,3], List = [A|B]. A = 1, B = [2,3], List = [1,2,3] ?- List = [A|B], A = 1, B = [2,3]. A = 1, B = [2,3], List = [1,2,3] formal models of computation
Lists (Cont’d) List . A B 1 . . 2 3 [] • The [H|T] notation allows for variations: • [E1,E2|T] (a list with at least 2 elements) • [1|[2,3|T]] (a list [1,2,3|T]) • [H|T] is also a term, hence a tree: ?- List = [A|B], A = 1, B = [2,3]. A = 1, B = [2,3], List = [1,2,3] formal models of computation
Haskell vs Prolog list notations formal models of computation
Operations on Lists L NewL . 1 . . 2 3 [] • We can access elements from the front of a list • We can add elements to the front of a list: ?- L = [2,3], NewL = [1|L] formal models of computation
Programming with Lists An argument is required to return the value of the list built • Because of their recursive definition, lists are naturally manipulated with recursive programs. • Let’s revisit our first loop program: • Enable it to build a list with values typed in loop(L):- % a loop to create a list L L = [X|Xs], % List L has form [X|Xs] read(X), % X is read from the standard Input (keyboard) X \= end, % if input different from “end” then loop(Xs). % build the tail recursively loop([]). % if input is “end”, then List is empty. ?- loop(MyList). |: a. |: b. |: end. MyList = [a,b] formal models of computation
Programming with Lists (Cont’d) . X1 Xs1 a . X2 Xs2 b . X3 Xs3 end • Its execution: loop(L):- L = [X|Xs], read(X), X \= end, loop(Xs). loop([]). loop(L1):- L1 = [X1|Xs1], read(X1), X1 \= end, loop(Xs1). L1 loop(L2):- L2 = [X2|Xs2], read(X2), X2 \= end, loop(Xs2). L2 ?- loop(MyList). |: a. ?- loop(MyList). |: a. |: b. |: end. ?- loop(MyList). |: a. |: b. ?- loop(MyList). loop(L3):- L3 = [X3|Xs3], read(X3), X3 \= end, loop(Xs3). loop(L3):- L3 = [X3|Xs3], read(X3), X3 \= end, loop(Xs3). L3 formal models of computation
Programming with Lists (Cont’d) . X1 Xs1 a . X2 Xs2 b [] • Its execution: loop(L):- L = [X|Xs], read(X), X \= end, loop(Xs). loop([]). loop(L1):- L1 = [X1|Xs1], read(X1), X1 \= end, loop(Xs1). L1 loop(L2):- L2 = [X2|Xs2], read(X2), X2 \= end, loop(Xs2). L2 ?- loop(MyList). |: a. |: b. |: end. MyList = [a,b] ?- loop(MyList). |: a. |: b. |: end. loop([]). formal models of computation
Programming with Lists (Cont’d) • To check if an element appears in a list: • Check if element is the head; • Otherwise, check if element appears in tail • This formulation is guaranteed to stop because lists are finite (!) and the process breaks list apart; • In Prolog (first formulation): member(X,List):- % X is a member of List List = [Y|Ys], % if List is of form [Y|Ys] X = Y. % and X is equal to Y member(X,List):- % otherwise (if not as above) List = [Y|Ys], % break the list apart member(X,Ys). % and check if X appears in Ys formal models of computation
Programming with Lists (Cont’d) • Second formulation: member(X,[Y|Ys]):- % X is a member of [Y|Ys] X = Y. % if X is equal to Y member(X,[Y|Ys]):- % otherwise (if not as above) member(X,Ys). % check if X appears in Ys formal models of computation
Programming with Lists (Cont’d) • Third (final) formulation: • General point: Uses of the built-in predicate = can almost always be replaced more elegantly with pattern-matching in the head of a clause. member(X,[X|_]). % X is member if it is head of list member(X,[_|Ys]):- % otherwise (if not as above) member(X,Ys). % check if X appears in Ys formal models of computation
Prolog vs Haskell • Cf Haskell member :: a -> [a] -> Bool member x [] = False member x (y:ys) = (x==y) || (member x ys) • Note: [] case member(X,[X|_]). member(X,[_|Ys]) :- member(X,Ys). formal models of computation
Programming with Lists (Cont’d) • Let’s run the previous program: member(X,[X|_]). member(X,[_|Ys]):- member(X,Ys). The query member(3,[1,2,3])does not match the 1st clause!! Prolog matches member(3,[1,2,3])with head of 2nd clause: member(X1,[_|Ys1]):- member(X1,Ys1) We thus get the values for the variables: {X1/3, Ys1/[2,3]} Prolog now tries to prove member(X1,Ys1) that is member(3,[2,3]) ?- member(3,[1,2,3]). ?- member(3,[1,2,3]). yes ?- The goal member(3,[2,3])does not match the 1st clause!! Prolog matches member(3,[2,3])with head of 2nd clause: member(X2,[_|Ys2]):- member(X2,Ys2) We thus get the values for the variables: {X2/3, Ys2/[3]} Prolog now tries to prove member(X2,Ys2) that is member(3,[3]) The query member(3,[3])matches the 1st clause!! formal models of computation
Programming with Lists (Cont’d) • Member program can be run in two ways: • To check if an element occurs in a list; • To obtain the elements of a list, one at a time • It depends on the query – the program is exactly the same!! • To obtain the elements of a list, we query: member(Elem,[1,2,3]) • We then get in Elem, the different elements ?- member(Elem,[1,2,3]). ?- member(Elem,[1,2,3]). Elem = 1 ? ?- member(Elem,[1,2,3]). Elem = 1 ? ; Elem = 2 ? ?- member(Elem,[1,2,3]). Elem = 1 ? ; Elem = 2 ? ; Elem = 3 ? ?- member(Elem,[1,2,3]). Elem = 1 ? ; Elem = 2 ? ; Elem = 3 ? ; no ?- Can you trace this query? formal models of computation
Programming with Lists (Cont’d) Xs Ys Zs . . . . . . 1 a 2 b [] [] • To concatenate two lists Xs and Ys: • Create a 3rd list Zs by copying all elements of Xs, • When we get to the end of Xs, put Ys as the tail of Zs • Diagrammatically: 1 2 formal models of computation
Programming with Lists (Cont’d) • We need a predicate with 3 arguments: • 1st argument is the list Xs • 2nd argument is the list Ys • 3rd argument is the concatenation Xs.Ys (in this order) • The first list Xs will control the loop • As the first list Xs is traversed, its elements are copied onto the 3rd argument (the concatenation). • When we get an empty list, we stop and make Ys the value of the 3rd argument formal models of computation
Programming with Lists (Cont’d) • First attempt: • A more compact format: append([],Ys,Zs):- % if Xs is empty, Zs = Ys. % then Zs is Ys append([X|Xs],Ys,[Z|Zs]):- % otherwise X = Z, % copy X onto 3rd argument append(Xs,Ys,Zs). % carry on recursively append([],Ys,Ys). % if Xs empty, then Zs is Ys append([X|Xs],Ys,[X|Zs]):- % else copy X onto 3rd arg. append(Xs,Ys,Zs). % carry on recursively formal models of computation
Programming with Lists (Cont’d) aliasing: Res/[X1|Zs1] aliasing: Zs1/[X2|Zs2] aliasing: Zs2/Ys3 • Execution: append([],Ys,Ys). append([X|Xs],Ys,[X|Zs]):- append(Xs,Ys,Zs). append([X1|Xs1],Ys1,[X1|Zs1]):- append(Xs1,Ys1,Zs1). {X1/1,Xs1/[2],Ys1/[a,b]} append([X1|Xs1],Ys1,[X1|Zs1]):- append(Xs1,Ys1,Zs1). {X1/1,Xs1/[2],Ys1/[a,b]} append([X1|Xs1],Ys1,[X1|Zs1]):- append(Xs1,Ys1,Zs1). ?- append([1,2],[a,b],Res). ?- append([1,2],[a,b],Res). Res = [1,2,a,b] ? yes ?- append([X2|Xs2],Ys2,[X2|Zs2]):- append(Xs2,Ys2,Zs2). append([X2|Xs2],Ys2,[X2|Zs2]):- append(Xs2,Ys2,Zs2). {X2/2,Xs2/[],Ys2/[a,b]} append([X2|Xs2],Ys2,[X2|Zs2]):- append(Xs2,Ys2,Zs2). {X2/2,Xs2/[],Ys2/[a,b]} append([],Ys3,Ys3). {Ys3/[a,b]} append([],Ys3,Ys3). formal models of computation
Programming with Lists (Cont’d) Res . X1 Zs1 . 1 X2 Zs2 . 2 . a b [] • Execution: append([],Ys,Ys). append([X|Xs],Ys,[X|Zs]):- append(Xs,Ys,Zs). append([X1|Xs1],Ys1,[X1|Zs1]):- append(Xs1,Ys1,Zs1). {X1/1,Xs1/[2],Ys1/[a,b]} ?- append([1,2],[a,b],Res). Res = [1,2,a,b] ? yes ?- append([X2|Xs2],Ys2,[X2|Zs2]):- append(Xs2,Ys2,Zs2). {X2/2,Xs2/[],Ys2/[a,b]} append([],Ys3,Ys3). {Ys3/[a,b]} formal models of computation
“Reversible definitions” in Prolog • As with “member”, “append” can be used in different ways, e.g. ?- append([a,b,c],[d],X). ?- append(X,[a,b,l,e],[e,a,t,a,b,l,e]). ?- append([e,a,t],X,[e,a,t,a,b,l,e]). ?- append(X,Y,[e,a,t,a,b,l,e]). • Each of these would require a different function/method definition in Haskell/Java… formal models of computation