My Gripes with Prolog

Hillel Wayne — Computer Things News

Summary

A blog post by Hillel Wayne detailing his frustrations with the Prolog programming language, including issues with strings, lack of functions, limited data types, and cuts.

<p>For the next release of <a href="https://leanpub.com/logic/" target="_blank">Logic for Programmers</a>, I'm finally adding the sections on Answer Set Programming and Constraint Logic Programming that I TODOd back in version 0.9. And this is making me re-experience some of my pain points with Prolog, which I will gripe about now. If you want to know more about why Prolog is cool instead, go <a href="https://buttondown.com/hillelwayne/archive/a48fce5b-8a05-4302-b620-9b26f057f145/" target="_blank">here</a> or <a href="https://www.metalevel.at/prolog" target="_blank">here</a> or <a href="https://ianthehenry.com/posts/drinking-with-datalog/" target="_blank">here</a> or <a href="https://logicprogramming.org/" target="_blank">here</a>. </p> <h3>No standardized strings</h3> <p>ISO "strings" are just atoms or lists of single-character atoms (or lists of integer character codes). The various implementations of Prolog add custom string operators but they are not cross compatible, so code written with strings in SWI-Prolog will not work in Scryer Prolog. </p> <h3>No functions</h3> <p>Code logic is expressed entirely in <em>rules</em>, predicates which return true or false for certain values. For example if you wanted to get the length of a Prolog list, you write this:</p> <div class="codehilite"><pre><span></span><code><span class="s s-Atom">?-</span> <span class="nf">length</span><span class="p">([</span><span class="s s-Atom">a</span><span class="p">,</span> <span class="s s-Atom">b</span><span class="p">,</span> <span class="s s-Atom">c</span><span class="p">],</span> <span class="nv">Len</span><span class="p">).</span> <span class="nv">Len</span> <span class="o">=</span> <span class="mf">3.</span> </code></pre></div> <p class="empty-line" style="height:16px; margin:0px !important;"></p> <p>Now this is pretty cool in that it allows bidirectionality, or running predicates "in reverse". To generate lists of length 3, you can write <code>length(L, 3)</code>. But it also means that if you want to get the length a list <em>plus one</em>, you can't do that in one expression, you have to write <code>length(List, Out), X is Out+1</code>.</p> <p>For a while I thought no functions was necessary evil for bidirectionality, but then I discovered <a href="https://picat-lang.org/" target="_blank">Picat</a> has functions and works just fine. That by itself is a reason for me to prefer Picat for my LP needs.</p> <p>(Bidirectionality is a killer feature of Prolog, so it's a shame I so rarely run into situations that use it.)</p> <h3>No standardized collection types besides lists</h3> <p>Aside from atoms (<code>abc</code>) and numbers, there are two data types:</p> <ul> <li>Linked lists like <code>[a,b,c,d]</code>.</li> <li>Compound terms like <code>dog(rex, poodle)</code>, which <em>seem</em> like record types but are actually tuples. You can even convert compound terms to linked lists with <code>=..</code>:</li> </ul> <div class="codehilite"><pre><span></span><code><span class="s s-Atom">?-</span> <span class="nv">L</span> <span class="s s-Atom">=..</span> <span class="p">[</span><span class="s s-Atom">a</span><span class="p">,</span> <span class="s s-Atom">b</span><span class="p">,</span> <span class="s s-Atom">c</span><span class="p">].</span> <span class="nv">L</span> <span class="o">=</span> <span class="nf">a</span><span class="p">(</span><span class="s s-Atom">b</span><span class="p">,</span> <span class="s s-Atom">c</span><span class="p">).</span> <span class="s s-Atom">?-</span> <span class="nf">a</span><span class="p">(</span><span class="s s-Atom">b</span><span class="p">,</span> <span class="nf">c</span><span class="p">(</span><span class="s s-Atom">c</span><span class="p">))</span> <span class="s s-Atom">=..</span> <span class="nv">L</span><span class="p">.</span> <span class="nv">L</span> <span class="o">=</span> <span class="p">[</span><span class="s s-Atom">a</span><span class="p">,</span> <span class="s s-Atom">b</span><span class="p">,</span> <span class="nf">c</span><span class="p">(</span><span class="s s-Atom">c</span><span class="p">)].</span> </code></pre></div> <p>There's no proper key-value maps or even struct types. Again, this is something that individual distributions can fix (without cross compatibility), but these never feel integrated with the rest of the language. </p> <h3>No boolean values</h3> <p><code>true</code> and <code>false</code> aren't values, they're control flow statements. <code>true</code> is a noop and <code>false</code> says that the current search path is a dead end, so backtrack and start again. You can't explicitly store true and false as values, you have to implicitly have them in facts (<code>passed(test)</code> instead of <code>test.passed? == true</code>).</p> <p>This hasn't made any tasks impossible, and I can usually find a workaround to whatever I want to do. But I do think it makes things more inconvenient! Sometimes I want to do something dumb like "get all atoms that don't pass at least three of these rules", and that'd be a lot easier if I could shove intermediate results into a sack of booleans. </p> <p>(This is called "<a href="https://en.wikipedia.org/wiki/Negation_as_failure" target="_blank">Negation as Failure</a>". I think this might be necessary to make Prolog a Turing complete general programming language. Picat fixes a lot of Prolog's gripes and still has negation as failure. ASP has regular negation but it's not Turing complete.) </p> <h3>Cuts are confusing</h3> <div class="subscribe-form"></div> <p>Prolog finds solutions through depth first search, and a "cut" (<code>!</code>) symbol prevents backtracking past a certain point. This is necessary for optimization but can lead to invalid programs. </p> <p>You're not supposed to use cuts if you can avoid it, so I pretended cuts didn't exist. Which is why I was surprised to find that <a href="https://eu.swi-prolog.org/pldoc/doc_for?object=(-%3E)/2" target="_blank">conditionals</a> are implemented with cuts. Because cuts are spooky dark magic conditionals <em>sometimes</em> conditionals work as I expect them to and sometimes leave out valid solutions and I have no idea how to tell which it'll be. Usually I find it safer to just avoid conditionals entirely, which means my code gets a lot longer and messier. </p> <h3>Non-cuts are confusing</h3> <p>The original example in the last section was this: </p> <div class="codehilite"><pre><span></span><code><span class="nf">foo</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="nv">B</span><span class="p">)</span> <span class="p">:-</span> <span class="s s-Atom">\+</span> <span class="p">(</span><span class="nv">A</span> <span class="o">=</span> <span class="nv">B</span><span class="p">),</span> <span class="nv">A</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">B</span> <span class="o">=</span> <span class="mf">2.</span> </code></pre></div> <p><code>foo(1, 2)</code> returns true, so you'd expect <code>f(A, B)</code> to return <code>A=1, B=2</code>. But it returns <code>false</code>. Whereas this works as expected.</p> <div class="codehilite"><pre><span></span><code><span class="nf">bar</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="nv">B</span><span class="p">)</span> <span class="p">:-</span> <span class="nv">A</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">B</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="s s-Atom">\+</span> <span class="p">(</span><span class="nv">A</span> <span class="o">=</span> <span class="nv">B</span><span class="p">).</span> </code></pre></div> <p>I <em>thought</em> this was because <code>\+</code> was implemented with cuts, and the <a href="https://www.amazon.com/Programming-Prolog-Using-ISO-Standard/dp/3540006788" target="_blank">Clocksin book</a> suggests it's <code>call(P), !, fail</code>, so this was my prime example about how cuts are confusing. But then I tried this:</p> <div class="codehilite"><pre><span></span><code><span class="s s-Atom">?-</span> <span class="nf">member</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">]),</span> <span class="s s-Atom">\+</span> <span class="p">(</span><span class="nv">A</span> <span class="o">=</span> <span class="mi">2</span><span class="p">),</span> <span class="nv">A</span> <span class="o">=</span> <span class="mf">3.</span> <span class="nv">A</span> <span class="o">=</span> <span class="mf">3.</span> <span class="c1">% wtf?</span> </code></pre></div> <p>There's no way to get that behavior with cuts! I don't think <code>\+</code> uses cuts at all! And now I have to figure out why <code>foo(A, B)</code> doesn't returns results. Is it <a href="https://github.com/dtonhofer/prolog_notes/blob/master/other_notes/about_negation/floundering.md" target="_blank">floundering</a>? Is it because <code>\+ P</code> only succeeds if <code>P</code> fails, and <code>A = B</code> always succeeds? A closed-world assumption? Something else?<sup id="fnref:dif"><a class="footnote-ref" href="#fn:dif">1</a></sup></p> <h3>Straying outside of default queries is confusing</h3> <p>Say I have a program like this:</p> <div class="codehilite"><pre><span></span><code><span class="nf">tree</span><span class="p">(</span><span class="s s-Atom">n</span><span class="p">,</span> <span class="s s-Atom">n1</span><span class="p">).</span> <span class="nf">tree</span><span class="p">(</span><span class="s s-Atom">n</span><span class="p">,</span> <span class="s s-Atom">n2</span><span class="p">).</span> <span class="nf">tree</span><span class="p">(</span><span class="s s-Atom">n1</span><span class="p">,</span> <span class="s s-Atom">n11</span><span class="p">).</span> <span class="nf">tree</span><span class="p">(</span><span class="s s-Atom">n2</span><span class="p">,</span> <span class="s s-Atom">n21</span><span class="p">).</span> <span class="nf">tree</span><span class="p">(</span><span class="s s-Atom">n2</span><span class="p">,</span> <span class="s s-Atom">n22</span><span class="p">).</span> <span class="nf">tree</span><span class="p">(</span><span class="s s-Atom">n11</span><span class="p">,</span> <span class="s s-Atom">n111</span><span class="p">).</span> <span class="nf">tree</span><span class="p">(</span><span class="s s-Atom">n11</span><span class="p">,</span> <span class="s s-Atom">n112</span><span class="p">).</span> <span class="nf">branch</span><span class="p">(</span><span class="nv">N</span><span class="p">)</span> <span class="p">:-</span> <span class="c1">% two children</span> <span class="nf">tree</span><span class="p">(</span><span class="nv">N</span><span class="p">,</span> <span class="nv">C1</span><span class="p">),</span> <span class="nf">tree</span><span class="p">(</span><span class="nv">N</span><span class="p">,</span> <span class="nv">C2</span><span class="p">),</span> <span class="nv">C1</span> <span class="s s-Atom">@&lt;</span> <span class="nv">C2</span><span class="p">.</span> <span class="c1">% ordering</span> </code></pre></div> <p>And I want to know all of the nodes that are parents of branches. The normal way to do this is with a query:</p> <div class="codehilite"><pre><span></span><code><span class="s s-Atom">?-</span> <span class="nf">tree</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="nv">N</span><span class="p">),</span> <span class="nf">branch</span><span class="p">(</span><span class="nv">N</span><span class="p">).</span> <span class="nv">A</span> <span class="o">=</span> <span class="s s-Atom">n</span><span class="p">,</span> <span class="nv">N</span> <span class="o">=</span> <span class="s s-Atom">n2</span><span class="p">;</span> <span class="c1">% show more...</span> <span class="nv">A</span> <span class="o">=</span> <span class="s s-Atom">n1</span><span class="p">,</span> <span class="nv">N</span> <span class="o">=</span> <span class="s s-Atom">n11</span><span class="p">.</span> </code></pre></div> <p>This is interactively making me query for every result. That's usually not what I want, I know the result of my query is finite and I want all of the results at once, so I can count or farble or whatever them. It took a while to figure out that the proper solution is <a href="https://www.swi-prolog.org/pldoc/man?predicate=bagof/3" target="_blank"><code>bagof(Template, Goal, Bag)</code></a>, which will "Unify Bag with the alternatives of Template":</p> <div class="codehilite"><pre><span></span><code><span class="s s-Atom">?-</span> <span class="nf">bagof</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="p">(</span><span class="nf">tree</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="nv">N</span><span class="p">),</span> <span class="nf">branch</span><span class="p">(</span><span class="nv">N</span><span class="p">)),</span> <span class="nv">As</span><span class="p">).</span> <span class="nv">As</span> <span class="o">=</span> <span class="p">[</span><span class="s s-Atom">n1</span><span class="p">],</span> <span class="nv">N</span> <span class="o">=</span> <span class="s s-Atom">n11</span><span class="p">;</span> <span class="nv">As</span> <span class="o">=</span> <span class="p">[</span><span class="s s-Atom">n</span><span class="p">],</span> <span class="nv">N</span> <span class="o">=</span> <span class="s s-Atom">n2</span><span class="p">.</span> </code></pre></div> <p>Wait crap that's still giving one result at a time, because <code>N</code> is a free variable in <code>bagof</code> so it backtracks over that. It surprises me but I guess it's good to have as an option. So how do I get all of the results at once?</p> <div class="codehilite"><pre><span></span><code><span class="s s-Atom">?-</span> <span class="nf">bagof</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="nv">N</span><span class="s s-Atom">^</span><span class="p">(</span><span class="nf">tree</span><span class="p">(</span><span class="nv">A</span><span class="p">,</span> <span class="nv">N</span><span class="p">),</span> <span class="nf">branch</span><span class="p">(</span><span class="nv">N</span><span class="p">)),</span> <span class="nv">As</span><span class="p">).</span> <span class="nv">As</span> <span class="o">=</span> <span class="p">[</span><span class="s s-Atom">n</span><span class="p">,</span> <span class="s s-Atom">n1</span><span class="p">]</span> </code></pre></div> <p>The only difference is the <code>N^Goal</code>, which tells <code>bagof</code> to ignore and group the results of <code>N</code>. As far as I can tell, this is the <em>only</em> place the ISO standard uses <code>^</code> to mean anything besides exponentiation. Supposedly it's the <a href="https://sicstus.sics.se/sicstus/docs/latest4/html/sicstus.html/ref_002dall_002dsum.html" target="_blank">existential quantifier</a>? In general whenever I try to stray outside simpler use-cases, especially if I try to do things non-interactively, I run into trouble.</p> <h3>I have mixed feelings about symbol terms</h3> <p>It took me a long time to realize the reason <code>bagof</code> "works" is because infix symbols are mapped to prefix compound terms, so that <code>a^b</code> is <code>^(a, b)</code>, and then different predicates can decide to do different things with <code>^(a, b)</code>.</p> <p>This is also why you can't just write <code>A = B+1</code>: that unifies <code>A</code> with the <em>compound term</em> <code>+(B, 1)</code>. <code>A+1 = B+2</code> is <em>false</em>, as <code>1 \= 2</code>. You have to write <code>A+1 is B+2</code>, as <code>is</code> is the operator that converts <code>+(B, 1)</code> to a mathematical term.</p> <p>(And <em>that</em> fails because <code>is</code> isn't fully bidirectional. The lhs <em>must</em> be a single variable. You have to import <code>clpfd</code> and write <code>A + 1 #= B + 2</code>.)</p> <p>I don't like this, but I'm a hypocrite for saying that because I appreciate the idea and don't mind custom symbols in other languages. I guess what annoys me is there's no official definition of what <code>^(a, b)</code> is, it's purely a convention. ISO Prolog uses <code>-(a, b)</code> (aka <code>a-b</code>) as a convention to mean "pairs", and the only way to realize that is to see that an awful lot of standard modules use that convention. But you can use <code>-(a, b)</code> to mean something else in your own code and nothing will warn you of the inconsistency.</p> <p>Anyway I griped about pairs so I can gripe about <code>sort</code>.</p> <h3>go home sort, ur drunk</h3> <p>This one's just a blunder:</p> <div class="codehilite"><pre><span></span><code><span class="s s-Atom">?-</span> <span class="nf">sort</span><span class="p">([</span><span class="mi">3</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">3</span><span class="p">],</span> <span class="nv">Out</span><span class="p">).</span> <span class="nv">Out</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">].</span> <span class="c1">% wat</span> </code></pre></div> <p>According to an expert online this is because sort is supposed to return a sorted <em>set</em>, not a sorted list. If you want to preserve duplicates you're supposed to lift all of the values into <code>-($key, $value)</code> compound terms, then use <a href="https://eu.swi-prolog.org/pldoc/doc_for?object=keysort/2" target="_blank">keysort</a>, then extract the values. And, since there's no functions, this process takes at least three lines. This is also how you're supposed to sort by a custom predicate, like "the second value of a compound term". </p> <p>(Most (but not all) distributions have a duplicate merge like <a href="https://eu.swi-prolog.org/pldoc/doc_for?object=msort/2" target="_blank">msort</a>. SWI-Prolog also has a <a href="https://eu.swi-prolog.org/pldoc/doc_for?object=predsort/3" target="_blank">sort by key</a> but it removes duplicates.)</p> <h3>Please just let me end rules with a trailing comma instead of a period, I'm begging you</h3> <p>I don't care if it makes fact parsing ambiguous, I just don't want "reorder two lines" to be a syntax error anymore</p> <hr/> <p>I expect by this time tomorrow I'll have been Cunningham'd and there will be a 2000 word essay about how all of my gripes are either easily fixable by doing XYZ or how they are the best possible choice that Prolog could have made. I mean, even in writing this I found out some fixes to problems I had. Like I was going to gripe about how I can't run SWI-Prolog queries from the command line but, in doing do diligence finally <em>finally</em> figured it out:</p> <div class="codehilite"><pre><span></span><code>swipl<span class="w"> </span>-t<span class="w"> </span>halt<span class="w"> </span>-g<span class="w"> </span><span class="s2">"bagof(X, Goal, Xs), print(Xs)"</span><span class="w"> </span>./file.pl </code></pre></div> <p>It's pretty clunky but still better than the old process of having to enter an interactive session every time I wanted to validate a script change.</p> <p>(Also, answer set programming is pretty darn cool. Excited to write about it in the book!)</p> <div class="footnote"> <hr/> <ol> <li id="fn:dif"> <p>A couple of people mentioned using <a href="https://eu.swi-prolog.org/pldoc/doc_for?object=dif/2" target="_blank">dif/2</a> instead of <code>\+ A = B</code>. Dif is great but usually I hit the negation footgun with things like <code>\+ foo(A, B), bar(B, C), baz(A, C)</code>, where <code>dif/2</code> isn't applicable. <a class="footnote-backref" href="#fnref:dif" title="Jump back to footnote 1 in the text">↩</a></p> </li> </ol> </div>
Original Article
View Cached Full Text

Cached at: 05/16/26, 03:40 AM

# My Gripes with Prolog Source: [https://buttondown.com/hillelwayne/archive/my-gripes-with-prolog](https://buttondown.com/hillelwayne/archive/my-gripes-with-prolog) For the next release of[Logic for Programmers](https://leanpub.com/logic/), I'm finally adding the sections on Answer Set Programming and Constraint Logic Programming that I TODOd back in version 0\.9\. And this is making me re\-experience some of my pain points with Prolog, which I will gripe about now\. If you want to know more about why Prolog is cool instead, go[here](https://buttondown.com/hillelwayne/archive/a48fce5b-8a05-4302-b620-9b26f057f145/)or[here](https://www.metalevel.at/prolog)or[here](https://ianthehenry.com/posts/drinking-with-datalog/)or[here](https://logicprogramming.org/)\. ### No standardized strings ISO "strings" are just atoms or lists of single\-character atoms \(or lists of integer character codes\)\. The various implementations of Prolog add custom string operators but they are not cross compatible, so code written with strings in SWI\-Prolog will not work in Scryer Prolog\. ### No functions Code logic is expressed entirely in*rules*, predicates which return true or false for certain values\. For example if you wanted to get the length of a Prolog list, you write this: ``` ?- length([a, b, c], Len). Len = 3. ``` Now this is pretty cool in that it allows bidirectionality, or running predicates "in reverse"\. To generate lists of length 3, you can write`length\(L, 3\)`\. But it also means that if you want to get the length a list*plus one*, you can't do that in one expression, you have to write`length\(List, Out\), X is Out\+1`\. For a while I thought no functions was necessary evil for bidirectionality, but then I discovered[Picat](https://picat-lang.org/)has functions and works just fine\. That by itself is a reason for me to prefer Picat for my LP needs\. \(Bidirectionality is a killer feature of Prolog, so it's a shame I so rarely run into situations that use it\.\) ### No standardized collection types besides lists Aside from atoms \(`abc`\) and numbers, there are two data types: - Linked lists like`\[a,b,c,d\]`\. - Compound terms like`dog\(rex, poodle\)`, which*seem*like record types but are actually tuples\. You can even convert compound terms to linked lists with`=\.\.`: ``` ?- L =.. [a, b, c]. L = a(b, c). ?- a(b, c(c)) =.. L. L = [a, b, c(c)]. ``` There's no proper key\-value maps or even struct types\. Again, this is something that individual distributions can fix \(without cross compatibility\), but these never feel integrated with the rest of the language\. ### No boolean values `true`and`false`aren't values, they're control flow statements\.`true`is a noop and`false`says that the current search path is a dead end, so backtrack and start again\. You can't explicitly store true and false as values, you have to implicitly have them in facts \(`passed\(test\)`instead of`test\.passed? == true`\)\. This hasn't made any tasks impossible, and I can usually find a workaround to whatever I want to do\. But I do think it makes things more inconvenient\! Sometimes I want to do something dumb like "get all atoms that don't pass at least three of these rules", and that'd be a lot easier if I could shove intermediate results into a sack of booleans\. \(This is called "[Negation as Failure](https://en.wikipedia.org/wiki/Negation_as_failure)"\. I think this might be necessary to make Prolog a Turing complete general programming language\. Picat fixes a lot of Prolog's gripes and still has negation as failure\. ASP has regular negation but it's not Turing complete\.\) ### Cuts are confusing Prolog finds solutions through depth first search, and a "cut" \(`\!`\) symbol prevents backtracking past a certain point\. This is necessary for optimization but can lead to invalid programs\. You're not supposed to use cuts if you can avoid it, so I pretended cuts didn't exist\. Which is why I was surprised to find that[conditionals](https://eu.swi-prolog.org/pldoc/doc_for?object=(-%3E)/2)are implemented with cuts\. Because cuts are spooky dark magic conditionals*sometimes*conditionals work as I expect them to and sometimes leave out valid solutions and I have no idea how to tell which it'll be\. Usually I find it safer to just avoid conditionals entirely, which means my code gets a lot longer and messier\. ### Non\-cuts are confusing The original example in the last section was this: ``` foo(A, B) :- \+ (A = B), A = 1, B = 2. ``` `foo\(1, 2\)`returns true, so you'd expect`f\(A, B\)`to return`A=1, B=2`\. But it returns`false`\. Whereas this works as expected\. ``` bar(A, B) :- A = 1, B = 2, \+ (A = B). ``` I*thought*this was because`\\\+`was implemented with cuts, and the[Clocksin book](https://www.amazon.com/Programming-Prolog-Using-ISO-Standard/dp/3540006788)suggests it's`call\(P\), \!, fail`, so this was my prime example about how cuts are confusing\. But then I tried this: ``` ?- member(A, [1,2,3]), \+ (A = 2), A = 3. A = 3. % wtf? ``` There's no way to get that behavior with cuts\! I don't think`\\\+`uses cuts at all\! And now I have to figure out why`foo\(A, B\)`doesn't returns results\. Is it[floundering](https://github.com/dtonhofer/prolog_notes/blob/master/other_notes/about_negation/floundering.md)? Is it because`\\\+ P`only succeeds if`P`fails, and`A = B`always succeeds? A closed\-world assumption? Something else?[1](https://buttondown.com/hillelwayne/archive/my-gripes-with-prolog#fn:dif) ### Straying outside of default queries is confusing Say I have a program like this: ``` tree(n, n1). tree(n, n2). tree(n1, n11). tree(n2, n21). tree(n2, n22). tree(n11, n111). tree(n11, n112). branch(N) :- % two children tree(N, C1), tree(N, C2), C1 @< C2. % ordering ``` And I want to know all of the nodes that are parents of branches\. The normal way to do this is with a query: ``` ?- tree(A, N), branch(N). A = n, N = n2; % show more... A = n1, N = n11. ``` This is interactively making me query for every result\. That's usually not what I want, I know the result of my query is finite and I want all of the results at once, so I can count or farble or whatever them\. It took a while to figure out that the proper solution is[`bagof\(Template, Goal, Bag\)`](https://www.swi-prolog.org/pldoc/man?predicate=bagof/3), which will "Unify Bag with the alternatives of Template": ``` ?- bagof(A, (tree(A, N), branch(N)), As). As = [n1], N = n11; As = [n], N = n2. ``` Wait crap that's still giving one result at a time, because`N`is a free variable in`bagof`so it backtracks over that\. It surprises me but I guess it's good to have as an option\. So how do I get all of the results at once? ``` ?- bagof(A, N^(tree(A, N), branch(N)), As). As = [n, n1] ``` The only difference is the`N^Goal`, which tells`bagof`to ignore and group the results of`N`\. As far as I can tell, this is the*only*place the ISO standard uses`^`to mean anything besides exponentiation\. Supposedly it's the[existential quantifier](https://sicstus.sics.se/sicstus/docs/latest4/html/sicstus.html/ref_002dall_002dsum.html)? In general whenever I try to stray outside simpler use\-cases, especially if I try to do things non\-interactively, I run into trouble\. ### I have mixed feelings about symbol terms It took me a long time to realize the reason`bagof`"works" is because infix symbols are mapped to prefix compound terms, so that`a^b`is`^\(a, b\)`, and then different predicates can decide to do different things with`^\(a, b\)`\. This is also why you can't just write`A = B\+1`: that unifies`A`with the*compound term*`\+\(B, 1\)`\.`A\+1 = B\+2`is*false*, as`1 \\= 2`\. You have to write`A\+1 is B\+2`, as`is`is the operator that converts`\+\(B, 1\)`to a mathematical term\. \(And*that*fails because`is`isn't fully bidirectional\. The lhs*must*be a single variable\. You have to import`clpfd`and write`A \+ 1 \#= B \+ 2`\.\) I don't like this, but I'm a hypocrite for saying that because I appreciate the idea and don't mind custom symbols in other languages\. I guess what annoys me is there's no official definition of what`^\(a, b\)`is, it's purely a convention\. ISO Prolog uses`\-\(a, b\)`\(aka`a\-b`\) as a convention to mean "pairs", and the only way to realize that is to see that an awful lot of standard modules use that convention\. But you can use`\-\(a, b\)`to mean something else in your own code and nothing will warn you of the inconsistency\. Anyway I griped about pairs so I can gripe about`sort`\. ### go home sort, ur drunk This one's just a blunder: ``` ?- sort([3,1,2,1,3], Out). Out = [1, 2, 3]. % wat ``` According to an expert online this is because sort is supposed to return a sorted*set*, not a sorted list\. If you want to preserve duplicates you're supposed to lift all of the values into`\-\($key, $value\)`compound terms, then use[keysort](https://eu.swi-prolog.org/pldoc/doc_for?object=keysort/2), then extract the values\. And, since there's no functions, this process takes at least three lines\. This is also how you're supposed to sort by a custom predicate, like "the second value of a compound term"\. \(Most \(but not all\) distributions have a duplicate merge like[msort](https://eu.swi-prolog.org/pldoc/doc_for?object=msort/2)\. SWI\-Prolog also has a[sort by key](https://eu.swi-prolog.org/pldoc/doc_for?object=predsort/3)but it removes duplicates\.\) ### Please just let me end rules with a trailing comma instead of a period, I'm begging you I don't care if it makes fact parsing ambiguous, I just don't want "reorder two lines" to be a syntax error anymore --- I expect by this time tomorrow I'll have been Cunningham'd and there will be a 2000 word essay about how all of my gripes are either easily fixable by doing XYZ or how they are the best possible choice that Prolog could have made\. I mean, even in writing this I found out some fixes to problems I had\. Like I was going to gripe about how I can't run SWI\-Prolog queries from the command line but, in doing do diligence finally*finally*figured it out: ``` swipl -t halt -g "bagof(X, Goal, Xs), print(Xs)" ./file.pl ``` It's pretty clunky but still better than the old process of having to enter an interactive session every time I wanted to validate a script change\. \(Also, answer set programming is pretty darn cool\. Excited to write about it in the book\!\)

Similar Articles

Prolog Coding Horror

Hacker News Top

A guide to common pitfalls in Prolog programming, emphasizing the use of pure and declarative constructs over impure ones like cuts, global state, and low-level I/O.

The Birth of Prolog (1996)

Hacker News Top

A retrospective article from 1996 on the origins and development of the Prolog programming language.

I Hate Compilers

Hacker News Top

A personal opinion piece expressing frustration with compilers, likely discussing their complexity or shortcomings.

Prolog Basics Explained with Pokémon

Lobsters Hottest

An introduction to Prolog programming using Pokémon type matchups as a motivating example, demonstrating how logic programming can elegantly model relational data.

New Blog Post: Some Silly Z3 Scripts I Wrote

Hillel Wayne — Computer Things

Hillel Wayne shares Z3 scripts he wrote, discussing challenges with logical properties and the concept of 'chaff' from his upcoming book Logic for Programmers.