[HN Gopher] Coding Guidelines for Prolog (2011) ___________________________________________________________________ Coding Guidelines for Prolog (2011) Author : tosh Score : 100 points Date : 2023-09-23 12:20 UTC (10 hours ago) (HTM) web link (arxiv.org) (TXT) w3m dump (arxiv.org) | [deleted] | gilcot wrote: | I've been doing Prolog without knowing there's a coding | guidelines. Thanks. | izwasm wrote: | I remember using this language in the AI class, i thought it was | an old, deprecated language as people now use python mainly, | really cool to see it again | rhelz wrote: | I forget who said it, but a good programming language should | have two features: | | 1. Make simple problems easy. | | 2. Make difficult problems possible. | | Python is an _outstanding_ language on both scores. | | Prolog, alas, is much better at #2 than it is at #1. For | example, prolog is a great choice for writing domain-specific | languages, and modern implementations (like scryer prolog, | mentioned in a comment above), can generate very efficient code | for them. | | Prolog's "killer ap" is how well it does on a third feature: | | 3. It makes virtually impossible tasks "merely" very difficult | ;-) | | And it has a steep learning curve; it took me years to really | "get" it, and I only persevered because I was facing one of | those category #3 problems. | | Its not a deprecated language, but alas, it is destined to be a | niche language. But for those niches, it is still the best, | hands-down. | bacteria wrote: | Can you tell us about the problem you've encountered in #3? | I'm really interested | ngruhn wrote: | I guess this is rather category #2 than #3, but my favorite | example is Advent of Code 2021, day 24: | https://adventofcode.com/2021/day/24. I think many people | consider this one of the hardest AOC problems ever and at | the time many people didn't even code up a complete | solution but solved in manually (me included). However, | with Prolog it's almost trivial. So many of Prologs | strengths come together here: writing parsers, writing | interpreters, solving integer constraints, and in | particular "reasoning backwards". | guessbest wrote: | I think it is undergoing a resurgence due to the LLM | prolifereating, but I haven't seen how Prolog is being used in | the LLM creation. Of course, I don't really understand the | whole process of the creation. I do remember someone on a | academic/programmer forum stating Prolog regarding research was | big in academics in Europe whereas Lisp was more popular in | America academics for research. It would make sense as Richard | Stallman was big into AI Lisp as a consultant. | triska wrote: | Very interesting! Let's consider for example the definition of | sum_list/2 which is shown in Fig. 1, Fig. 2 and Fig. 3: | %% sum_list(+Number_List, ?Result) % Unifies Result with | the sum of the numbers in Number_List; % calls error/1 if | Number_List is not a list of numbers. | sum_list(Number_List, Result) :- | sum_list(Number_List, 0, Result). % | sum_list(+Number_List, +Accumulator, ?Result) | sum_list([], A, A). % At end: unify with accumulator. | sum_list([H|T], A, R) :- % Accumulate first and recur. | number(H), !, B is A + H, | sum_list(Rest, B, R). sum_list(_, _A, _R) :- % Catch ill- | formed arguments. error('first arg to sum_list/2 | not a list of numbers'). | | Compiling it with Scryer Prolog, I get a warning: | $ scryer_prolog sum_list.pl Warning: singleton variables | T, Rest at line 4 of sum_list.pl true. | | A singleton variable often indicates a mistake in the code. And | indeed, the sample code uses Rest where it apparently meant to | use T (or vice versa). So, I change the second clause of | sum_list/3 to: sum_list([H|T], A, R) :- | number(H), !, B is A + H, | sum_list(T, B, R). | | And now we're ready to use the predicate! Let's ask Prolog the | most general query: _Which answers are there in general?_ So, a | query where all arguments are logic variables: | ?- sum_list(Ls, R). Ls = [], R = 0 ; | error(existence_error(procedure,error/1),error/1). | | The existence error is due to the use of the non-standard | predicate error/1 in the code sample. The predicate apparently | meant to throw an exception telling us: first | arg to sum_list/2 not a list of numbers | | But _the query did not restrict the first argument at all_ , so | it may just as well be a list of numbers! The predicate probably | meant to say that the argument is _not sufficiently | instantiated_. In that case, it should have thrown an | _instantiation error_. The standard predicate (is) /2 throws such | an instantiation error for us in such cases. Also, it throws | _type errors_ for us! A type error is categorically different | from an instantiation error: From a logical perspective, a type | error can be replaced by silent failure, but an instantiation | error can not. | | We can therefore write the second clause as: | sum_list([H|T], A, R) :- B is A + H, | sum_list(T, B, R). | | and also remove the third clause entirely. We now get: | ?- sum_list(Ls, R). Ls = [], R = 0 ; | error(instantiation_error,(is)/2). | | From a logical perspective, that's OK: The predicate tells us | that too little is known to make any statement, and a more | specific query may yield solutions. | | Also, this version correctly distinguishes between type and | instantiation errors, and we now get for example: | ?- sum_list("abc", R). | error(type_error(evaluable,a),(is)/2). | | As I see it, a key attraction of logic programming is that we are | able to reason logically about our code. This holds as long as | certain logical properties are preserved. The paper hints at such | properties for example with the concept of _steadfastness_ , | which it defines in Section 5.1: A predicate " _must work | correctly if its output variable already happens to be | instantiated to the output value_ ". How can we tell though which | variables are output variables, and also why even distinguish a | particular variable as "output variable"? Should this not hold | for _all_ variables? | | A particularly important logical property is called | _monotonicity_ : Generalizing a query (or program) can at most | add solutions, never remove them. With monotonic predicates, | debugging is very nice: For instance, if a predicate unexpectedly | fails, then we can _generalize_ it by removing goals, and if the | remaining fragment _still_ fails unexpectedly, then there must be | a mistake _in that fragment_. Scryer Prolog provides | library(debug) for this approach of _declarative debugging_ : | | https://www.scryer.pl/debug | | In Scryer Prolog, we can define the specific case of arithmetic | over integers with _constraints_ over integers, for example like | this: :- use_module(library(clpz)). :- | use_module(library(lists)). :- | use_module(library(lambda)). list_sum(Is, S) :- | foldl(\I^S0^S^(S #= S0 + I), Is, 0, S). | | Higher-order predicates such as maplist/N and foldl/N retain | logical properties of the predicates that occur as arguments. | | The most general query now works as expected: | ?- list_sum(Is, Sum). Is = [], Sum = 0 ; Is = | [Sum], clpz:(Sum in inf..sup) ; Is = [_A,_B], | clpz:(_A+_B#=Sum) ; Is = [_A,_B,_D], clpz:(_A+_B#=_C), | clpz:(_C+_D#=Sum) ; ... . | | The predicate does not terminate, _as expected_ , because we | expect solutions for lists of _all lengths_ : | ?- list_sum(Is, Sum), false. loops. | | And other cases are simply specific _instances_ of the most | general query: ?- list_sum([1,2,3], Sum). | Sum = 6. | | Note that I have changed the predicate name from sum_list/2 to | list_sum/2, because the list is the _first_ argument, and the sum | is the _second_ argument. So, I am now using "sum" no longer as | a _verb_ , but as a _noun_ , because that seems more appropriate | for code that is declarative, not imperative: We describe what is | true, not what must be done, and our code works in all directions | and also with different execution strategies. integers_sum/2 may | be an even better name in this case. | | One other naming convention I like to use is to append an "s" for | logic variables that stand for lists, such as "Is" for a list of | integers. | rhelz wrote: | Great post, showing how these coding guidelines are showing | their age. So much boilerplate comments, which clearly are not | pulling their weight. A simple coding guideline like "eliminate | all singleton variable warnings" would serve better, and it is | enforced automatically to boot. | | One thing we have learned is that if a coding guideline is | mandatory, it just has to be automatically checkable and | enforceable. | Avshalom wrote: | >does not terminate, as expected | | okay but generally we want things to terminate. An infinite | list of useless solutions is almost never what people want in | an actual program. | | which also ties into the definition of "the output variable", | which is the information we wanted when we wrote the predicate. | crabbone wrote: | > okay but generally we want things to terminate. | | Not in this case. In this case the author claims that if | there are infinitely many solutions, the predicate should | give infinitely many solutions. It's similar to how you want | functions in languages that use functions to fail | _predictably_. Eg. you want to receive ENOENT exit code if | you try to open a file that doesn 't exist. Or, you want to | block forever if you join a thread that runs an infinite | loop. | | Do you want to have any of those behaviors in your program? | -- rarely, and sometimes not at all. But you want your | program to fail in such a way that would indicate that you | coded it in a particular way that should result in such an | error. | ngruhn wrote: | I think he's just using it as a test here. He forces infinite | backtracking by adding false | | to the query. You wouldn't do that in an actual program. | abecedarius wrote: | I'm not sure if this example is meant to be a great one -- they | introduce it to illustrate different ways of typesetting. | | _The Craft of Prolog_ by one of the authors gave similar | general advice to yours, as I remember it. (Good book.) | | I've only glanced over this paper. | Andrew018 wrote: | [dead] ___________________________________________________________________ (page generated 2023-09-23 23:00 UTC)