<div class="notebook"> <div class="nb-cell html" name="htm1"> <h1>The Prolog Lightbulb Joke</h1> <p> This is a SWISH version of <a href="http://www.j-paine.org/dobbs/prolog_lightbulb.html">my Prolog tutorial for Dr Dobbs</a> called "The Prolog Lightbulb Joke". </p><p> I'm going to explain enough Prolog for you to understand the Prolog lightbulb joke. </p><p> Prolog stands for "Programming in Logic". Here is a tiny program, which asserts a fact and a rule. Together, these make two-thirds of a well-known syllogism:</p> </div> <div class="nb-cell program" data-background="true" data-singleline="true" name="p1"> is_a( socrates, human ). is_a( X, mortal ) :- is_a( X, human ). </div> <div class="nb-cell html" name="htm2"> <p> Let's read the fact as "Socrates is human": names starting with a lower-case letter are constants standing for specific things or concepts. The rule means "for any X, X is mortal if X is human". Names starting with upper-case letters are variables. </p><p> Now let's try running it. I ask whether the syllogism's conclusion holds, to which Prolog replies that it does:</p> </div> <div class="nb-cell query" name="q1"> is_a( socrates, human ). </div> <div class="nb-cell html" name="htm3"> <p> Here are Prolog's replies to some other queries:</p> </div> <div class="nb-cell query" name="q2"> is_a( Person, human ). </div> <div class="nb-cell markdown" name="md1"> </div> <div class="nb-cell program" name="p6"> </div> <div class="nb-cell query" name="q8"> </div> <div class="nb-cell query" name="q3"> is_a( Person, mortal ). </div> <div class="nb-cell html" name="htm4"> <p> The first query asks Prolog whether the stated relation holds. The second two ask it to find some Person — another variable — for whom the stated relation holds. </p><p> Let's do some list processing. This is another Prolog program:</p> </div> <div class="nb-cell program" name="p2"> borders( belgium, france ). borders( france, spain ). borders( germany, austria ). borders( germany, denmark ). borders( germany, switzerland ). borders( netherlands, belgium ). borders( netherlands, germany ). borders( spain, portugal ). route( A, B ) :- borders( A, B ). route( A, B ) :- borders( A, Z ), route( Z, B ). route( A, B, [ A,B ] ) :- borders( A, B ). route( A, B, [ A | ZtoB ] ) :- borders( A, Z ), route( Z, B, ZtoB ). </div> <div class="nb-cell html" name="htm5"> <p> I'll interpret the first rule as: there is a route from A to B if A borders B. The second, as: there is a route from A to B if A borders some Z, and there is a route from that Z to B. </p><p> In 2002, I took a train from Maastricht in Holland to Braga in Portugal: I'd been watching the Euro-welcoming ceremony in Maastricht, and wanted to get from there to Portugal by 1 pm on the following Saturday to see how the market-stall holders in Braga's first post-Euro market were coping with the changeover. If this seems perverse, let's just say that I'd stayed in Braga before, and knew that the town market closed at 1 pm each Saturday. Let's ask Prolog whether the route itself is feasible:</p> </div> <div class="nb-cell query" name="q4"> route(netherlands,portugal, R). </div> <div class="nb-cell html" name="htm6"> <p> What's that? There's no such route? Must be something wrong with my code. Oh, I see: I mistyped "spain" as "spian" in the above file. So Prolog couldn't infer a route from the facts, and replied "false". Some Prologs don't reply "false", but "no". It means the same: no answer can be proven. This kind of thing can happen in other ways. For example, if I'd typed "broders" instead of "borders" in one of those facts, "route" would have ignored it, and my query would have answered "false" in the same way. </p><p> Now I've edited "spian" to "spain", and I'm going to reload my facts and ask again whether there's a route:</p> </div> <div class="nb-cell program" data-singleline="true" name="p3"> </div> <div class="nb-cell query" name="q5"> route(netherlands,portugal). </div> <div class="nb-cell html" name="htm7"> <p> But what is that route? Here's an amended definition of "route" that will calculate it for me:</p> </div> <div class="nb-cell program" name="p4"> kobieta(beata). kobieta(dorota). kobieta(maria). kobieta(ewa). kobieta(anna). kobieta(hanna). kobieta(aneta). kobieta(agnieszka). mezczyzna(adam). mezczyzna(michal). mezczyzna(jan). mezczyzna(piotr). mezczyzna(dominik). mezczyzna(marcin). mezczyzna(karol). mezczyzna(krzysztof). mezczyzna(kacper). rodzic(beata, piotr). rodzic(adam, piotr). rodzic(dorota, anna). rodzic(michal, anna). rodzic(jan, ewa). rodzic(maria, ewa). rodzic(piotr, dominik). rodzic(anna, dominik). rodzic(piotr, marcin). rodzic(anna, marcin). rodzic(ewa, karol). rodzic(dominik, karol). rodzic(ewa, krzysztof). rodzic(dominik, krzysztof). rodzic(ewa, kacper). rodzic(dominik, kacper). rodzic(ewa, hanna). rodzic(dominik, hanna). rodzic(marcin, agnieszka). rodzic(aneta, agnieszka). route( A, B, [ go(A,B) ] ) :- borders( A, B ). route( A, B, [ go(A,Z) | ZtoB ] ) :- borders( A, Z ), route( Z, B, ZtoB ). </div> <div class="nb-cell html" name="htm8"> <p> Now, I've given "route" an extra, third, argument. I intend this to be an output argument, analogous to the result of a function: "route" will put something into it. The first clause for "route" puts "[ go(A,B) ]" into it. This is a one-element list. The square brackets say it's a list, and its single element is "go(A,B)". Logicians and Prolog programmers call "go(A,B)" a "term". It's like a structure or record, with two fields, distinguished by position rather than by name. The first field will become whatever A holds; the second, whatever B holds. </p><p> Whenever possible, Prolog programmers read programs as sets of logical statements, not as commands. So here's a logical reading for the first clause for "route": if A borders B, the route from A to B is the list containing the record "go(A,B)". And here's a logical reading for the second clause: if A borders some Z, and there's a route ZtoB from Z to B, then the route from A to B is the list holding the record "go(A,Z)" followed by ZtoB. Square brackets indicate a list, as before; vertical bar means "followed by". Could recursion ever be more concise? Let's see it run:</p> </div> <div class="nb-cell query" name="q6"> route(netherlands,portugal,R). </div> <div class="nb-cell html" name="htm9"> <p> That "route" predicate generated a list, putting it into an output argument. But predicates can also take lists as inputs, as I'll show now. A textbook author — possibly Patrick Henry Winston — once wrote the following as an example to help novices understand recursion: </p><blockquote> <p> This is how to tidy up your room:<br> if there are no toys lying around,<br> do nothing.<br> if there is a toy lying around,<br> pick it up and put it in the toybox,<br> and tidy up your room. </p> </blockquote> <p> To render this as Prolog. I'll write a predicate called "tidy" with two arguments. The first, an input, will be a list of toys. The second, an output, will be a list of the commands a robot must obey in order to tidy them. Here's my predicate:</p> </div> <div class="nb-cell program" name="p5"> tidy( [], [] ). tidy( [ Toy | OtherToys ], [ pick_up(Toy), move_to(toybox), drop(Toy) | OtherCommands ] ) :- tidy( OtherToys, OtherCommands ). </div> <div class="nb-cell html" name="htm10"> I'll read the first clause as meaning: if the list of toys to tidy is empty, the list of commands needed is empty. And the second as: if the list of toys starts with any toy (a variable) which we'll call Toy, and continues with OtherToys, and the commands needed to tidy OtherToys are OtherCommands, then the complete list of commands needed is "pick_up(Toy)" followed by "move_to(toybox)" followed by "drop(Toy)" followed by OtherCommands. </div> <div class="nb-cell query" name="q7"> tidy( [ teddy, ball, golly, bat ], Cmds ). </div> <div class="nb-cell program" name="p7"> </div> <div class="nb-cell program" data-background="true" name="p8"> kobieta(beata). kobieta(dorota). kobieta(maria). kobieta(ewa). kobieta(anna). kobieta(hanna). kobieta(aneta). kobieta(agnieszka). mezczyzna(adam). mezczyzna(michal). mezczyzna(jan). mezczyzna(piotr). mezczyzna(dominik). mezczyzna(marcin). mezczyzna(karol). mezczyzna(krzysztof). mezczyzna(kacper). rodzic(beata, piotr). rodzic(adam, piotr). rodzic(dorota, anna). rodzic(michal, anna). rodzic(jan, ewa). rodzic(maria, ewa). rodzic(piotr, dominik). rodzic(anna, dominik). rodzic(piotr, marcin). rodzic(anna, marcin). rodzic(ewa, karol). rodzic(dominik, karol). rodzic(ewa, krzysztof). rodzic(dominik, krzysztof). rodzic(ewa, kacper). rodzic(dominik, kacper). rodzic(ewa, hanna). rodzic(dominik, hanna). rodzic(marcin, agnieszka). rodzic(aneta, agnieszka). brat1(X,Y) :- not(X=Y), rodzic(Z,X), rodzic(Z,Y), mezczyzna(X). brat2(X,Y) :- rodzic(Z,X), not(X=Y), rodzic(Z,Y), mezczyzna(X). brat3(X,Y) :- rodzic(Z,X), rodzic(Z,Y), not(X=Y), mezczyzna(X). rrr(X) :- X, !. </div> </div>