From 4b5a50ebfd82efc1c57e6a842ad22116b5ba5ccf Mon Sep 17 00:00:00 2001 From: hyphz Date: Tue, 18 Jul 2017 18:36:03 +0100 Subject: [PATCH] A first try at prolog. --- prolog.html.markdown | 331 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 prolog.html.markdown diff --git a/prolog.html.markdown b/prolog.html.markdown new file mode 100644 index 00000000..f37d29cb --- /dev/null +++ b/prolog.html.markdown @@ -0,0 +1,331 @@ +--- +language: prolog +contributors: + - ["hyphz", "http://github.com/hyphz/"] +filename: learnprolog.pl +--- + +Prolog is a logic programming language first specified in 1972, and refined into multiple modern implementations. + +``` +% This is a comment. + +% Prolog treats code entered in interactive mode differently +% to code entered in a file and loaded ("consulted"). +% This code must be loaded from a file to work as intended. +% Lines that begin with ?- can be typed in interactive mode. +% A bunch of errors and warnings will trigger when you load this file +% due to the examples which are supposed to fail - they can be safely +% ignored. + +% Output is based on SWI-prolog 7.2.3. Different Prologs may behave +% differently. + +% Prolog is based on the ideal of logic programming. +% A subprogram (called a predicate) represents a state of the world. +% A command (called a goal) tells Prolog to make that state of the world +% come true, if possible. + +% As an example, here is a definition of the simplest kind of predicate: +% a fact. + +magicNumber(7). +magicNumber(9). +magicNumber(42). + +% This introduces magicNumber as a predicate and says that it is true +% with parameter 7, 9, or 42, but no other parameter. Note that +% predicate names must start with lower case letters. We can now use +% interactive mode to ask if it is true for different values: + +?- magicNumber(7). % True +?- magicNumber(8). % False +?- magicNumber(9). % True + +% Some older Prologs may display "Yes" and "No" instead of True and +% False. + +% What makes Prolog unusual is that we can also tell Prolog to _make_ +% magicNumber true, by passing it an undefined variable. Any name +% starting with a capital letter is a variable in Prolog. + +?- magicNumber(Presto). % Presto = 7 ; + % Presto = 9 ; + % Presto = 42. + +% Prolog makes magicNumber true by assigning one of the valid numbers to +% the undefined variable Presto. By default it assigns the first one, 7. +% By pressing ; in interactive mode you can reject that solution and +% force it to assign the next one, 9. Pressing ; again forces it to try +% the last one, 42, after which it no longer accepts input because this +% is the last solution. You can accept an earlier solution by pressing . +% instead of ;. + +% This is Prolog's central operation: unification. Unification is +% essentially a combination of assignment and equality! It works as +% follows: +% If both sides are bound (ie, defined), check equality. +% If one side is free (ie, undefined), assign to match the other side. +% If both sides are free, abort because this can't be resolved. +% The = sign in Prolog represents unification, so: + +?- 2 = 3. % False - equality test +?- X = 3. % X = 3 - assignment +?- X = 2, X = Y. % X = Y = 2 - two assignments + % Note Y is assigned to, even though it is + % on the right hand side, because it is free +?- X = 3, X = 2. % False + % First acts as assignment and binds X=3 + % Second acts as equality because X is bound + % Since 3 does not equal 2, gives False + % Thus in Prolog variables are immutable +?- X = 3+2. % X = 3+2 - unification can't do arithmetic +?- X is 3+2. % X = 5 - "is" does arithmetic. +?- 5 = X+2. % This is why = can't do arithmetic - + % because Prolog can't solve equations +?- 5 is X+2. % Error. Unlike =, the right hand side of IS + % must always be bound, thus guaranteeing + % no attempt to solve an equation. + +% Any unification, and thus any predicate in Prolog, can either: +% Succeed (return True) without changing anything, +% because an equality-style unification was true +% Succeed (return True) and bind one or more variables in the process, +% because an assignment-style unification was made true +% or Fail (return False) +% because an equality-style unification was false +% (Failure can never bind variables) + +% The ideal of being able to give any predicate as a goal and have it +% made true is not always possible, but can be worked toward. For +% example, Prolog has a built in predicate plus which represents +% arithmetic addition but can reverse simple additions. + +?- plus(1, 2, 3). % True +?- plus(1, 2, X). % X = 3 because 1+2 = X. +?- plus(1, X, 3). % X = 2 because 1+X = 3. +?- plus(X, 2, 3). % X = 1 because X+1 = 3. +?- plus(X, 5, Y). % Error - although this could be solved, + % the number of solutions is infinite, + % which most predicates try to avoid. + +% When a predicate such as magicNumber can give several solutions, the +% overall compound goal including it may have several solutions too. + +?- magicNumber(X), plus(X,Y,100). % X = 7, Y = 93 ; + % X = 9, Y = 91 ; + % X = 42, Y = 58 . +% Note: on this occasion it works to pass two variables to plus because +% only Y is free (X is bound by magicNumber). + +% However, if one of the goals is fully bound and thus acts as a test, +% then solutions which fail the test are rejected. +?- magicNumber(X), X > 40. % X = 42 +?- magicNumber(X), X > 100. % False + +% To see how Prolog actually handles this, let's introduce the print +% predicate. Print always succeeds, never binds any variables, and +% prints out its parameter as a side effect. + +?- print("Hello"). % "Hello" true. +?- X = 2, print(X). % 2 true. +?- X = 2, print(X), X = 3. % 2 false - print happens immediately when + % it is encountered, even though the overall + % compound goal fails (because 2 != 3, + % see the example above). + +% By using Print we can see what actually happens when we give a +% compound goal including a test that sometimes fails. +?- magicNumber(X), print(X), X > 40. % 7 9 42 X = 42 . + +% MagicNumber(X) unifies X with its first possibility, 7. +% Print(X) prints out 7. +% X > 40 tests if 7 > 40. It is not, so it fails. +% However, Prolog remembers that magicNumber(X) offered multiple +% solutions. So it _backtracks_ to that point in the code to try +% the next solution, X = 9. +% Having backtracked it must work through the compound goal +% again from that point including the Print(X). So Print(X) prints out +% 9. +% X > 40 tests if 9 > 40 and fails again. +% Prolog remembers that magicNumber(X) still has solutions and +% backtracks. Now X = 42. +% It works through the Print(X) again and prints 42. +% X > 40 tests if 42 > 40 and succeeds so the result bound to X +% The same backtracking process is used when you reject a result at +% the interactive prompt by pressing ;, for example: + +?- magicNumber(X), print(X), X > 8. % 7 9 X = 9 ; + % 42 X = 42. + +% As you saw above we can define our own simple predicates as facts. +% More complex predicates are defined as rules, like this: + +nearby(X,Y) :- X = Y. +nearby(X,Y) :- Y is X+1. +nearby(X,Y) :- Y is X-1. + +% nearby(X,Y) is true if Y is X plus or minus 1. +% However this predicate could be improved. Here's why: + +?- nearby(2,3). % True ; False. +% Because we have three possible definitions, Prolog sees this as 3 +% possibilities. X = Y fails, so Y is X+1 is then tried and succeeds, +% giving the True answer. But Prolog still remembers there are more +% possibilities for nearby() (in Prolog terminology, "it has a +% choice point") even though "Y is X-1" is doomed to fail, and gives us +% the option of rejecting the True answer, which doesn't make a whole +% lot of sense. + +?- nearby(4, X). % X = 4 ; + % X = 5 ; + % X = 3. Great, this works +?- nearby(X, 4). % X = 4 ; + % error +% After rejecting X = 4 prolog backtracks and tries "Y is X+1" which is +% "4 is X+1" after substitution of parameters. But as we know from above +% "is" requires its argument to be fully instantiated and it is not, so +% an error occurs. + +% One way to solve the first problem is to use a construct called the +% cut, !, which does nothing but which cannot be backtracked past. + +nearbychk(X,Y) :- X = Y, !. +nearbychk(X,Y) :- Y is X+1, !. +nearbychk(X,Y) :- Y is X-1. + +% This solves the first problem: +?- nearbychk(2,3). % True. + +% But unfortunately it has consequences: +?- nearbychk(2,X). % X = 2. +% Because Prolog cannot backtrack past the cut after X = Y, it cannot +% try the possibilities "Y is X+1" and "Y is X-1", so it only generates +% one solution when there should be 3. +% However if our only interest is in checking if numbers are nearby, +% this may be all we need, thus the name nearbychk. +% This structure is used in Prolog itself from time to time (for example +% in list membership). + +% To solve the second problem we can use built-in predicates in Prolog +% to verify if a parameter is bound or free and adjust our calculations +% appropriately. +nearby2(X,Y) :- nonvar(X), X = Y. +nearby2(X,Y) :- nonvar(X), Y is X+1. +nearby2(X,Y) :- nonvar(X), Y is X-1. +nearby2(X,Y) :- var(X), nonvar(Y), nearby2(Y,X). + +% We can combine this with a cut in the case where both variables are +% bound, to solve both problems. +nearby3(X,Y) :- nonvar(X), nonvar(Y), nearby2(X,Y), !. +nearby3(X,Y) :- nearby2(X,Y). + +% However when writing a predicate it is not normally necessary to go to +% these lengths to perfectly support every possible parameter +% combination. It suffices to support parameter combinations we need to +% use in the program. It is a good idea to document which combinations +% are supported. In regular Prolog this is informally in structured +% comments, but in some Prolog variants like Visual Prolog and Mercury +% this is mandatory and checked by the compiler. + +% Here is the structured comment declaration for nearby3: + +%% nearby3(+X:Int, +Y:Int) is semidet. +%% nearby3(+X:Int, -Y:Int) is multi. +%% nearby3(-X:Int, +Y:Int) is multi. + +% For each variable we list a type. The + or - before the variable name +% indicates if the parameter is bound (+) or free (-). The word after +% "is" describes the behaviour of the predicate: +% semidet - can succeed once or fail +% ( Two specific numbers are either nearby or not ) +% multi - can succeed multiple times but cannot fail +% ( One number surely has at least 3 nearby numbers ) +% Other possibilities are: +% det - always succeeds exactly once (eg, print) +% nondet - can succeed multiple times or fail. +% In Prolog these are just structured comments and strictly informal but +% extremely useful. + +% An unusual feature of Prolog is its support for atoms. Atoms are +% essentially members of an enumerated type that are created on demand +% whenever an unquoted non variable value is used. For example: +character(batman). % Creates atom value batman +character(robin). % Creates atom value robin +character(joker). % Creates atom value joker +character(darthVader). % Creates atom value darthVader +?- batman = batman. % True - Once created value is reused +?- batman = batMan. % False - atoms are case sensitive +?- batman = darthVader. % False - atoms are distinct + +% Atoms are popular in examples but were created on the assumption that +% Prolog would be used interactively by end users - they are less +% useful for modern applications and some Prolog variants abolish them +% completely. However they can be very useful internally. + +% Loops in Prolog are classically written using recursion. +% Note that below, writeln is used instead of print because print is +% intended for debugging. + +%% countTo(+X:Int) is det. +%% countUpTo(+Value:Int, +Limit:Int) is det. +countTo(X) :- countUpTo(1,X). +countUpTo(Value, Limit) :- Value = Limit, writeln(Value), !. +countUpTo(Value, Limit) :- Value \= Limit, writeln(Value), + NextValue is Value+1, + countUpTo(NextValue, Limit). + +?- countTo(10). % Outputs 1 to 10 + +% Note the use of multiple declarations in countUpTo to create an +% IF test. If Value = Limit fails the second declaration is run. +% There is also a more elegant syntax. + +%% countUpTo2(+Value:Int, +Limit:Int) is det. +countUpTo2(Value, Limit) :- writeln(Value), + Value = Limit -> true ; ( + NextValue is Value+1, + countUpTo2(NextValue, Limit)). + +?- countUpTo2(1,10). % Outputs 1 to 10 + +% If a predicate returns multiple times it is often useful to loop +% through all the values it returns. Older Prologs used a hideous syntax +% called a "failure-driven loop" to do this, but newer ones use a higher +% order function. + +%% countTo2(+X:Int) is det. +countTo2(X) :- forall(between(1,X,Y),writeln(Y)). + +?- countTo2(10). % Outputs 1 to 10 + +% Lists are given in square brackets. Use memberchk to check membership. +% A group is safe if it doesn't include Joker or does include Batman. +%% safe(Group:list(atom)) is det. +safe(Group) :- memberchk(joker, Group) -> memberchk(batman, Group) ; true. + +?- safe([robin]). % True +?- safe([joker]). % False +?- safe([joker, batman]). % True + +% The member predicate works like memberchk if both arguments are bound, +% but can accept free variables and thus can be used to loop through +% lists. + +?- member(X, [1,2,3]). % X = 1 ; X = 2 ; X = 3 . +?- forall(member(X,[1,2,3]), + (Y is X+1, writeln(Y))). % 2 3 4 + +% The maplist function can be used to generate lists based on other +% lists. Note that the output list is a free variable, causing an +% undefined value to be passed to plus, which is then bound by +% unification. Also notice the use of currying on the plus predicate - +% it's a 3 argument predicate, but we specify only the first, because +% the second and third are filled in by maplist. + +?- maplist(plus(1), [2,3,4], Output). % Output = [3, 4, 5]. +``` + +##Ready For More? + +* [SWI-Prolog](http://www.swi-prolog.org/)