{"categories":["Principles of Programming Languages"],"contentHtml":"<p>Conditional statements are very powerful and are in used in almost every language. How does Prolog implement the same idea?</p>\n<h2>Logic + Control</h2>\n<p>The basic idea behind a control statement is that there is <em>logic</em>, such as facts, rules, and queries, which are composed of clauses/<strong>goals</strong>, as well as <em>control</em>, which is how Prolog chooses the logic among several options. The control is implemented by the order of the facts/goals, which is <strong>extremely important</strong>. Prolog will <em>always</em> choose the first applicable rule in a program, and it will <em>always</em> choose the leftmost clause/goal in a query.</p>\n<p>Brevity is key. If there are two rules that do the same thing, or one rule that is implied by another rule, you should probably take it out unless executing something more than once is your explicit intention.</p>\n<h2>Abstract Interpreter</h2>\n<p><strong>A substitution \\(σ\\) is a finite set of pairs of terms \\(\\{X_1/t_1, ..., X_n/t_n\\}\\) where each \\(t_i\\) is a term and each \\(X_i\\) is a variable such that \\(X_i ≠ t_i\\) and \\(X_i ≠ X_j\\) if \\(i ≠ j\\).</strong></p>\n<p>An empty substitution is denoted by the letter \\(ε\\).</p>\n<p>Some important rules:</p>\n<ul>\n<li>\n<p>A variable cannot substitute itself e.g. \\(Z/Z\\) is illegal.</p>\n</li>\n<li>\n<p>Only a variable can be substituted e.g. \\(m/n\\) is illegal because \\(m\\) is an atom.</p>\n</li>\n</ul>\n<p>The meaning of applying a term to a substitution is that every occurrence of \\(X_i\\) in the compound term is replaced with the corresponding substituent in \\(σ\\) simultaneously. This application is known as <strong>instantiation</strong>, and that new compound term \\(Eσ\\) is an <strong>instance</strong>.</p>\n<p>Bringing back the earlier rules about unification, you can see how unification is derived from substitution. Variables can unify with anything, so they are the term that is substituted in Prolog. There is an exception to this, which is known as the <strong>occurs check</strong>.</p>\n<p>We can use a substitution \\(σ\\) as a <strong>unifier</strong> for two terms if the application of \\(σ\\) to those terms makes them <em>syntactically equal</em>. This is distinct from <em>semantic</em> equality. When we talk about unifiers, we are only talking about the actual letters, not the meaning of the term. For \\(S = f(X,Y)\\) and \\(T=f(g(Z),Z)\\), if we have a \\(σ = \\{X/g(Z),Y/Z\\}\\) then \\(S\\) and \\(T\\) will be unified. Since the unification only cares about syntax, more than one unifier may exist for two terms. We could have just as well substituted the other way and it would still be a unifier.</p>\n<h2>Unifier Computation</h2>\n<pre><code>unify(X, Y, θ) =\n  X = Xθ\n  Y = Yθ\n  case X is a variable that does not occur in Y:\n&nbsp;&nbsp;&nbsp;&nbsp;return (θ{X/Y} ∪ {X/Y}) // this replaces X with Y in the unifier, and then adds a new substitution just in case X will appear later\n  case Y is a variable that does not occur in X:\n&nbsp;&nbsp;&nbsp;&nbsp;return (θ{Y/X} ∪ {Y/X}) // this replaces Y with X in the unifier. this is a rare case when the variables substitute each other\n  case X and Y are identical constants\n&nbsp;&nbsp;&nbsp;&nbsp;return Θ\n&nbsp;&nbsp;case X and Y are compound terms like f(X1, ..., Xn) and f(Y1, ..., Yn)\n&nbsp;&nbsp;&nbsp;&nbsp;return (fold_left (fun Θ (X,Y) -&gt; unify(X, Y, Θ)) θ [(X1, Y1), ..., (Xn, Yn)]\n&nbsp;&nbsp;&nbsp;&nbsp;// this applies the unification process to the compound terms\n&nbsp;&nbsp;&nbsp;&nbsp;// the function is just folding left with θ as acc and the set of compound terms as the list\n</code></pre>\n<p>This is the basic pseudocode for the unify function. It's recursive, and side-effect free. However, it's not complete for writing an interpreter. For that, we also need backtracking.</p>\n<h2>Backtracking</h2>\n<p>The resolvent will maintain a list to satisfy our query. When we try to resolve a compound term, any goals within will be queued onto the resolvent to be resolved. This is <em>non-deterministic</em>, there is no order that will necessarily be followed other than that sub-goals within a single term will be inorder.</p>\n<h2>Missionaries and Cannibals</h2>\n<p>Let's imagine a problem where we have three missionaries and three cannibals that need to cross a river in one boat. If the cannibals outnumber the missionaries, they will get eaten. How can we get every person across the river?</p>\n<p>The concept of a <em>safe state</em> will be important here. A state is safe when no missionaries are eaten. By labelling certain states as unsafe, we can cut those paths out of our search algorithm. We should also define <em>transitions</em> between states, where a predicate moves a state from A to B.</p>\n<p>Our states are essentially a pair representing position.</p>\n<pre><code>start(3-3-0-0-l).\nfinish(0-0-3-3-_).\n</code></pre>\n<p>The elements in order are missionaries on the original side, cannibals on the original side, missionaries on the target side, cannibals on the other side, and the location of the boat.</p>\n<p>The safety of the state is based on whether there are more cannibals than missionaries.</p>\n<pre><code>safe(0-_-M2-C2-_) :- M2 &gt;= C2.\nsafe(M1-C1-0-_-_) :- M1 &gt;= C1.\nsafe(M1-C1-M2-C2-_) :- M1 &gt;= C1, M2 &gt;= C2.\n</code></pre>\n<p>Every state here represents a point at which there are at least as many missionaries as cannibals on each side of the river.</p>\n<p>The change in state can be caused by a <code>carry/2</code> predicate which details how many missionaries and cannibals are being moved, respectively. In our case, the boat can only move up to 2 people so we need to represent every possible case of that.</p>\n<pre><code>carry(2, 0).\ncarry(1, 1).\ncarry(0, 2).\ncarry(1, 0).\ncarry(0, 1).\n</code></pre>\n<p>However, we need a way to represent moving on both sides of the river, so we will have two transitions.</p>\n<pre><code>step(M1-C1-M2-C2-l, M3-C3-M4-C4-r) :-\n    carry(X, Y),\n    M1 &gt;= X, M3 is M1 - X, M4 is M2 + X,\n    C1 &gt;= Y, C3 is C1 - Y, C4 is C2 + Y.\n\nstep(M1-C1-M2-C2-r, M3-C3-M4-C4-l) :-\n    carry(X, Y),\n    M2 &gt;= X, M4 is M2 - X, M3 is M1 + X,\n    C2 &gt;= Y, C4 is C2 - Y, C3 is C1 + Y.\n</code></pre>\n<p>This <code>step</code> predicate checks every valid <code>carry</code>, then will execute that transition from the current state.</p>\n<pre><code>travel(A, A, _, []).\ntravel(A, C, Visited, [B | Steps]) :-\n</code></pre>","contentMarkdown":"Conditional statements are very powerful and are in used in almost every language. How does Prolog implement the same idea?\n\n## Logic + Control\n\nThe basic idea behind a control statement is that there is *logic*, such as facts, rules, and queries, which are composed of clauses/**goals**, as well as *control*, which is how Prolog chooses the logic among several options. The control is implemented by the order of the facts/goals, which is **extremely important**. Prolog will *always* choose the first applicable rule in a program, and it will *always* choose the leftmost clause/goal in a query.\n\nBrevity is key. If there are two rules that do the same thing, or one rule that is implied by another rule, you should probably take it out unless executing something more than once is your explicit intention.\n\n## Abstract Interpreter\n\n**A substitution \\\\(σ\\\\) is a finite set of pairs of terms \\\\(\\\\{X_1/t_1, ..., X_n/t_n\\\\}\\\\) where each \\\\(t_i\\\\) is a term and each \\\\(X_i\\\\) is a variable such that \\\\(X_i ≠ t_i\\\\) and \\\\(X_i ≠ X_j\\\\) if \\\\(i ≠ j\\\\).**\n\nAn empty substitution is denoted by the letter \\\\(ε\\\\).\n\nSome important rules:\n\n- A variable cannot substitute itself e.g. \\\\(Z/Z\\\\) is illegal.\n\n- Only a variable can be substituted e.g. \\\\(m/n\\\\) is illegal because \\\\(m\\\\) is an atom.\n\nThe meaning of applying a term to a substitution is that every occurrence of \\\\(X_i\\\\) in the compound term is replaced with the corresponding substituent in \\\\(σ\\\\) simultaneously. This application is known as **instantiation**, and that new compound term \\\\(Eσ\\\\) is an **instance**.\n\nBringing back the earlier rules about unification, you can see how unification is derived from substitution. Variables can unify with anything, so they are the term that is substituted in Prolog. There is an exception to this, which is known as the **occurs check**.\n\nWe can use a substitution \\\\(σ\\\\) as a **unifier** for two terms if the application of \\\\(σ\\\\) to those terms makes them *syntactically equal*. This is distinct from *semantic* equality. When we talk about unifiers, we are only talking about the actual letters, not the meaning of the term. For \\\\(S = f(X,Y)\\\\) and \\\\(T=f(g(Z),Z)\\\\), if we have a \\\\(σ = \\\\{X/g(Z),Y/Z\\\\}\\\\) then \\\\(S\\\\) and \\\\(T\\\\) will be unified. Since the unification only cares about syntax, more than one unifier may exist for two terms. We could have just as well substituted the other way and it would still be a unifier.\n\n## Unifier Computation\n\n```\nunify(X, Y, θ) =\n  X = Xθ\n  Y = Yθ\n  case X is a variable that does not occur in Y:\n    return (θ{X/Y} ∪ {X/Y}) // this replaces X with Y in the unifier, and then adds a new substitution just in case X will appear later\n  case Y is a variable that does not occur in X:\n    return (θ{Y/X} ∪ {Y/X}) // this replaces Y with X in the unifier. this is a rare case when the variables substitute each other\n  case X and Y are identical constants\n    return Θ\n  case X and Y are compound terms like f(X1, ..., Xn) and f(Y1, ..., Yn)\n    return (fold_left (fun Θ (X,Y) -> unify(X, Y, Θ)) θ [(X1, Y1), ..., (Xn, Yn)]\n    // this applies the unification process to the compound terms\n    // the function is just folding left with θ as acc and the set of compound terms as the list\n```\n\nThis is the basic pseudocode for the unify function. It's recursive, and side-effect free. However, it's not complete for writing an interpreter. For that, we also need backtracking.\n\n## Backtracking\n\nThe resolvent will maintain a list to satisfy our query. When we try to resolve a compound term, any goals within will be queued onto the resolvent to be resolved. This is *non-deterministic*, there is no order that will necessarily be followed other than that sub-goals within a single term will be inorder.\n\n## Missionaries and Cannibals\n\nLet's imagine a problem where we have three missionaries and three cannibals that need to cross a river in one boat. If the cannibals outnumber the missionaries, they will get eaten. How can we get every person across the river?\n\nThe concept of a *safe state* will be important here. A state is safe when no missionaries are eaten. By labelling certain states as unsafe, we can cut those paths out of our search algorithm. We should also define *transitions* between states, where a predicate moves a state from A to B. \n\nOur states are essentially a pair representing position.\n\n```pro\nstart(3-3-0-0-l).\nfinish(0-0-3-3-_).\n```\n\nThe elements in order are missionaries on the original side, cannibals on the original side, missionaries on the target side, cannibals on the other side, and the location of the boat.\n\nThe safety of the state is based on whether there are more cannibals than missionaries.\n\n```pro\nsafe(0-_-M2-C2-_) :- M2 >= C2.\nsafe(M1-C1-0-_-_) :- M1 >= C1.\nsafe(M1-C1-M2-C2-_) :- M1 >= C1, M2 >= C2.\n```\n\nEvery state here represents a point at which there are at least as many missionaries as cannibals on each side of the river.\n\nThe change in state can be caused by a `carry/2` predicate which details how many missionaries and cannibals are being moved, respectively. In our case, the boat can only move up to 2 people so we need to represent every possible case of that.\n\n```pro\ncarry(2, 0).\ncarry(1, 1).\ncarry(0, 2).\ncarry(1, 0).\ncarry(0, 1).\n```\n\nHowever, we need a way to represent moving on both sides of the river, so we will have two transitions.\n\n```prolog\nstep(M1-C1-M2-C2-l, M3-C3-M4-C4-r) :-\n    carry(X, Y),\n    M1 >= X, M3 is M1 - X, M4 is M2 + X,\n    C1 >= Y, C3 is C1 - Y, C4 is C2 + Y.\n\nstep(M1-C1-M2-C2-r, M3-C3-M4-C4-l) :-\n    carry(X, Y),\n    M2 >= X, M4 is M2 - X, M3 is M1 + X,\n    C2 >= Y, C4 is C2 - Y, C3 is C1 + Y.\n```\n\nThis `step` predicate checks every valid `carry`, then will execute that transition from the current state.\n\n```pro\ntravel(A, A, _, []).\ntravel(A, C, Visited, [B | Steps]) :-\n```","dataUrl":"https://sharifhsn.dev/api/posts/prolog-control.json","date":"2022-04-05","datePublished":"2022-04-05","description":"Conditional statements are very powerful and are in used in almost every language. How does Prolog implement the same idea?","site":"https://sharifhsn.dev","slug":"prolog-control","source":"Archive","sourceUrl":null,"tags":["Internet Technology","Principles of Programming Languages"],"title":"Control in Prolog","url":"https://sharifhsn.dev/blog/prolog-control/","version":"1","wordCount":1009}