PageRenderTime 21ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/notes/10_cuts_and_negation.md

http://github.com/lorenzo-stoakes/learn-prolog-now
Markdown | 439 lines | 316 code | 123 blank | 0 comment | 0 complexity | 8c82bf0705c6ca05b0114f1cca9c672c MD5 | raw file
  1. <link href="http://kevinburke.bitbucket.org/markdowncss/markdown.css" rel="stylesheet"></link>
  2. Learn Prolog Now!
  3. =================
  4. Notes for
  5. [chapter 10](http://www.learnprolognow.org/lpnpage.php?pagetype=html&pageid=lpn-htmlch10).
  6. I use [swipl](http://www.swi-prolog.org/) as my prolog interpreter.
  7. Chapter 10 - Cuts and Negation
  8. ------------------------------
  9. 10.1 The Cut
  10. ------------
  11. * Automatic backtracking is one of the most characteristic features of Prolog, but it can lead
  12. to inefficiency.
  13. * So far, we've seen different ways of changing backtracking behaviour - changing the rule
  14. order and changing the goal order. Both of these are rather crude.
  15. * There is another way - the built-in predicate __!__, called *cut*, which provides a more
  16. direct way of controlling the way Prolog looks for solutions.
  17. * Cut is essentially a special atom we can use to write clauses.
  18. E.g.:-
  19. ```prolog
  20. p(X):- b(X), c(X), !, d(X), e(X).
  21. ```
  22. * Cut always succeeds, and has a side effect - if some goal makes use of this clause (the
  23. *parent goal*), then the cut commits Prolog to any choices that were made since the parent
  24. *goal was unified with the left hand side of the rule (including the choice of using that
  25. *particular clause too).
  26. * Let's consider an example.
  27. Firstly, without a cut:-
  28. ```prolog
  29. p(X):- a(X).
  30. p(X):- b(X), c(X), d(X), e(X).
  31. p(X):- f(X).
  32. a(1).
  33. b(1).
  34. b(2).
  35. c(1).
  36. c(2).
  37. d(2).
  38. e(2).
  39. f(3).
  40. ?- p(X).
  41. X = 1 ;
  42. X = 2 ;
  43. X = 3.
  44. ```
  45. The search tree for this knowledge base:-
  46. <img src="http://www.learnprolognow.org/html/chap10-pspic1.ps.png" />
  47. Now let's insert a cut into the second clause:-
  48. ```prolog
  49. p(X):- b(X), c(X), !, d(X), e(X).
  50. ?- p(X).
  51. X = 1 ;
  52. false.
  53. ```
  54. What's happening?
  55. 1. __p(X)__ is unified with the first rule, so we get __a(X)__. By instantiating __X__ to 1,
  56. Prolog unifies __a(X)__ with __a(1)__, and we have a solution. So far, this is no different
  57. from the first version.
  58. 2. We then look for a second solution. __p(X)__ is unified with the second rule, so we get the
  59. new goals __b(X)__, __c(X)__, __!__, __d(X)__, __e(X)__. Prolog then unifies __b(X)__ with the
  60. fact __b(1)__, so we now have the goals __c(1)__, __!__, __d(1)__, __e(1)__. We already have
  61. __c(1)__ in the database so this simplifies to __!__, __d(1)__, __e(1)__.
  62. 3. Now things change - __!__ succeeds (as it always does), and commits us to the choices made
  63. so far. Particularly, we commit to having __X = 1__, and we're also committed to using the
  64. second rule.
  65. 4. __d(1)__ fails. We can't try __X=2__, as __!__ has committed us to our decisions so far, and
  66. nor can we try __X=3__ for the same reason, so there's no way to satisfy __p(X)__.
  67. In terms of the search tree we have:-
  68. <img src="http://www.learnprolognow.org/html/chap10-pspic4.ps.png" />
  69. Search stops when the goal __d(1)__ doesn't lead to any node when an alternative choice is
  70. available. The crosses indicate branches which have been trimmed away.
  71. * An important point to consider is that the cut only commits us to the choices made since the
  72. parent goal was unified with the left hand side of the clause containing the cut.
  73. E.g., for:-
  74. ```prolog
  75. q:- p1, ..., pn, !, r1, ..., rm
  76. ```
  77. When we encounter the cut we are committed to using this particular clause for __q__ and the
  78. choices made when evaluating __p1__, ..., __pn__. However, we can backtrack as much as we like
  79. over __r1__, ..., __rm__, and we can also backtrack among choices made before reaching __q__.
  80. * Let's look at a concrete example.
  81. The knowledge base:-
  82. ```prolog
  83. s(X, Y):- q(X, Y).
  84. s(0, 0).
  85. q(X, Y):- i(X), j(Y).
  86. i(1).
  87. i(2).
  88. j(1).
  89. j(2).
  90. j(3).
  91. ```
  92. Querying this KB:-
  93. ```prolog
  94. s(X, Y).
  95. X = Y, Y = 1 ;
  96. X = 1,
  97. Y = 2 ;
  98. X = 1,
  99. Y = 3 ;
  100. X = 2,
  101. Y = 1 ;
  102. X = Y, Y = 2 ;
  103. X = 2,
  104. Y = 3 ;
  105. X = Y, Y = 0.
  106. ```
  107. With this corresponding search tree:-
  108. <img src="http://www.learnprolognow.org/html/chap10-pspic5.ps.png" />
  109. * Now let's add a cut:-
  110. ```prolog
  111. q(X,Y):- i(X), !, j(Y).
  112. ```
  113. This results in:-
  114. ```prolog
  115. ?- s(X, Y).
  116. X = Y, Y = 1 ;
  117. X = 1,
  118. Y = 2 ;
  119. X = 1,
  120. Y = 3 ;
  121. X = Y, Y = 0.
  122. ```
  123. This is because:-
  124. 1. __s(X,Y)__ is first unified with the first rule, which gives us a new goal of __q(X,Y)__.
  125. 2. __q(X,Y)__ is then unified with the third rule, so our new goal is __i(X), !, j(Y)__. We
  126. instantiate __X__ to 1, therefore unifying __i(X)__ to __i(1)__. We then end up with the goal
  127. __!,j(Y)__. The cut succeeds (of course), and we are committed to the choices we've made so
  128. far.
  129. 3. What are these choices we've committed to? That a. __X=1__, and that we're using this
  130. clause. Note that we haven't chosen a value for __Y__.
  131. 4. We instantiate __Y__ to 1 and thus __j(Y)__ unifies to __j(1)__.
  132. 5. We then backtrack and obtain __Y=2__, and __Y=3__.
  133. 7. These are alternatives for __j(X)__. Backtracking to the left of the cut is not permitted,
  134. so we can't reset __X__ to 2, so we don't find those three solutions that the non-cut program
  135. found. Backtracking over goals which were reached before __q(X,Y)__ *is* allowed, so Prolog
  136. will find the second clause for __s/2__.
  137. And the equivalent search tree is:-
  138. <img src="http://www.learnprolognow.org/html/chap10-pspic6.ps.png" />
  139. 10.2 Using Cut
  140. --------------
  141. * So now we know what cut is, but how do we use it in practice, and why is it so useful?
  142. * Let's look at an example of a cut-free predicate __max/3__ which takes integers as arguments
  143. and succeeds if the third argument is a maximum of the first two.
  144. E.g., we should expect the following queries to succeed:-
  145. ```prolog
  146. ?- max(2,3,3).
  147. ?- max(3,2,3).
  148. ?- max(3,3,3).
  149. ```
  150. And the following queries should fail:-
  151. ```prolog
  152. ?- max(2,3,2).
  153. ?- max(2,3,5).
  154. ```
  155. Typically we'd use it thus:-
  156. ```prolog
  157. ?- max(2,3,Max).
  158. Max = 3.
  159. ```
  160. We could write this thus:-
  161. ```prolog
  162. max(X,Y,X):- X >= Y.
  163. max(X,Y,Y):- Y > X.
  164. ```
  165. * This is a perfectly fine program, only we find that Prolog will attempt to backtrack
  166. unnecessarily in the case of __Y>X__. E.g.:-
  167. ```prolog
  168. ?- max(3,2,X).
  169. X = 3 ;
  170. false.
  171. ```
  172. * The two clauses in this program are mutually exclusive. If the first succeeds, the second
  173. must fail and vice-versa. Attempting to re-satisfy this clause is a waste of time.
  174. * We can fix this with cut. E.g.:-
  175. ```prolog
  176. max(X,Y,X):- X >= Y, !.
  177. max(X,Y,Y):- Y > X.
  178. ```
  179. And we get:-
  180. ```prolog
  181. ?- max(3,2,X).
  182. X = 3.
  183. ```
  184. * We might think we can be clever and skip out the second clauses altogether. E.g.:-
  185. ```prolog
  186. max(X,Y,X):- X >= Y, !.
  187. max(X,Y,Y).
  188. ```
  189. However this isn't as clever as it seems. Consider:-
  190. ```prolog
  191. ?- max(3,2,2).
  192. true.
  193. ```
  194. This is obviously invalid. What's happened here is that the query has failed to unify at all
  195. with the first clause, but has trivially unified with the second one.
  196. * We don't have this problem with a variable in the third place, as we actually find the
  197. max in these cases.
  198. * How do we work around this? Our problem was carrying out variable unification before we
  199. traversed the cut. We can work around this by introducing a third variable, so our first
  200. clause actually gets evaluated.
  201. ```prolog
  202. max(X,Y,Z):- X >= Y, !, Z = X.
  203. max(X,Y,Y).
  204. ```
  205. This works, e.g.:-
  206. ```prolog
  207. ?- max(3,2,2).
  208. false.
  209. ```
  210. * One important thing to note here is that this is a *red* cut, whereas our initial __max__
  211. program was a *green* cut. A red cut is, by definition, one whose absence changes the meaning
  212. of the program, while a green cut does not change its meaning.
  213. * Red cuts are dangerous because they make the code ever more less declarative, and more
  214. imperative, therefore it can be confusing as to how the program is actually going to behave.
  215. * The best approach to writing cuts in code is to get a non-cut program working, and only then
  216. try to improve its efficiency by using cuts.
  217. 10.3 Negation as Failure
  218. ------------------------
  219. * One of Prolog's most useful features is the simple means by which it lets us state
  220. generalisations. If we want to say that Vincent enjoys burgers then we write. E.g.:-
  221. ```prolog
  222. enjoys(vincent,X):- burger(X).
  223. ```
  224. * However, in real life we have exceptions. How do we represent this in Prolog? E.g., consider
  225. the case where Vincent enjoys burgers, but not Big Kahuna burgers. How do we state this in
  226. Prolog?
  227. * To begin with, let's look at the built-in predicate __fail/0__. This immediately fails when
  228. Prolog encounters it as a goal.
  229. * Keep in mind that once Prolog fails, it tries to backtrack. As a result, __fail/0__ can be
  230. considered an instruction to force immediate backtracking. Using this in combination with
  231. cut, which blocks backtracking, we are able to use this to write some interesting problems,
  232. particularly defining exceptions to general rules.
  233. Consider the following KB:-
  234. ```prolog
  235. enjoys(vincent,X):- big_kahuna_burger(X),!,fail.
  236. enjoys(vincent,X):- burger(X).
  237. burger(X):- big_mac(X).
  238. burger(X):- big_kahuna_burger(b).
  239. burger(X):- whopper(X).
  240. big_mac(a).
  241. big_kahuna_burger(b).
  242. big_mac(c).
  243. whopper(d).
  244. ```
  245. * From this, we can see that our condition, i.e. that vincent does not like Big Kahuna burgers,
  246. is applied. E.g.:-
  247. ```prolog
  248. ?- enjoys(vincent,a).
  249. true.
  250. ?- enjoys(vincent,b).
  251. false.
  252. ?- enjoys(vincent,c).
  253. true.
  254. ?- enjoys(vincent,d).
  255. true.
  256. ```
  257. * The reason this works is the combination of __!__ and __fail/0__ - the *cut-fail
  258. combination*. When we run the query __enjoys(vincent,b)__, the first rule applies, and we
  259. reach the cut. This commits us to the choices we've made, most importantly of course blocking
  260. the second rule. When we hit __fail/0__, we are forced to backtrack, but the cut prevents it
  261. so our query fails.
  262. * There is a problem here - the ordering of the rules is vital. If we reverse the first two
  263. rules, then the meaning of this program changes. Note also that if we remove the cut, the
  264. program doesn't work properly - a red cut.
  265. * It'd be better if we could extract this to make the program less reliant on the procedural
  266. qualities of the program.
  267. * We can abstract this idea away. E.g.:-
  268. ```prolog
  269. neg(Goal):- Goal,!,fail.
  270. neg(Goal).
  271. ```
  272. This will succeed if __Goal__ does not succeed. So we can replace the first rule with:-
  273. ```prolog
  274. enjoys(vincent,X):- burger(X), neg(big_kahuna_burger(X)).
  275. ```
  276. * Negation as failure is an important tool - it's expressive (we can describe exceptions),
  277. while being relatively 'safe'. Using this, rather than using the lower-level cut form helps
  278. us avoid the programming errors that often go along with red cuts.
  279. * In fact, it's so useful that it often comes built in as the __\\+__ operator. E.g.:-
  280. ```prolog
  281. enjoys(vincent,X):- burger(X), \+ big_kahuna_burger(X).
  282. ```
  283. * We have to be careful with the order of things again, e.g. if we rewrote our rule. E.g.:-
  284. ```prolog
  285. enjoys(vincent,X):- \+ big_kahuna_burger(X), burger(X).
  286. ```
  287. And if we then pose the query, we get:-
  288. ```prolog
  289. ?- enjoys(vincent,X).
  290. false.
  291. ```
  292. * This fails because __big\_kahuna\_burger(X)__ holds as Prolog unifies __X__ to b. If we
  293. trace:-
  294. ```prolog
  295. [trace] ?- enjoys(vincent,X).
  296. Call: (6) enjoys(vincent, _G560) ?
  297. Call: (7) big_kahuna_burger(_G560) ?
  298. Exit: (7) big_kahuna_burger(b) ?
  299. Fail: (6) enjoys(vincent, _G560) ?
  300. false.
  301. ```
  302. * It's generally better to use the *negation as failure* operator than red cuts, but this isn't
  303. always the case. As always, perf hacks are the exception to the rule.
  304. * Consider the case where we want to capture the condition: 'p holds if a and b hold or a does
  305. not hold and c holds too.' We can capture this as:-
  306. ```prolog
  307. p:- a, b.
  308. p:- \+ a, c.
  309. ```
  310. * However, suppose this is a very complicated goal which takes a lot of time to compute - we
  311. might end up having to compute __a__ twice. We can use a red cut to lock into choosing __a__
  312. and write this as:-
  313. ```prolog
  314. p:- a,!,b.
  315. p:- c.
  316. ```