Recently started woring with prolog and decided to make a text documenting my annoyances

This isn’t my first rodeo with prolog but it’s probably the longest time that I’ve been using it so far.

While working on a chess engine written in prolog I’ve come across a few annoyances in the language. Nothing has been bad enough to dissuade me from continuing (it’s a hobby project after all). But coming from other (mostly imperative) languages there I’m starting to notice a few things that I really miss in the language.

I’m posting this here to read the thoughts of the prolog veterans.

Thanks for sharing. It is always good to have fresh comments. Several have a work-around.

column(Table, Column, Info)

As shown by @j4n_bur53. You can use term_expansion to make this work. The problem is of course that f(X=a(Y)) or f(X@a(Y)) have a meaning as is, and you are changing this. SWI-Prolog handles unifications to head arguments that immediately follow the head efficiently. So code like below uses clause indexing and does not create a copy of X. If you need a lot of that, I’d indeed introduce some abbreviated syntax. Possibly we should have a library for that.

f(X, ...) :-
    X = f(Y),
    ....
    g(X).

my_pred(…) :-
rule1,
rule2, % This comma is ignored
.

The standard trick is to use true. as last line. Of have an editor that is smart enough. I’m not aware of such an editor :frowning:

my_pred(…):-
rule1,
skip,
rule2. % Only rule 1 is checked

You can do that. Define a module skip with the code below. The operator precedence causes skip to take the remainder of the clause as argument. Next just just define it to be true. Also works in branches of a disjunction.

:- module(skip,
          [ (skip)/1,
            op(1000, fy, (skip))
          ]).

skip(_).

Type hints

Typing is a long debated topic. It probably needs to be reopened. In the old days we had fully typed languages. Now we have typing on dynamically typed languages such as typescript for JavaScript, types for Python, etc. The Ciao system is probably most advanced here. Hopefully something simple and useful will emerge …

.

2 Likes

The problem is of course that f(X=a(Y)) or f(X@a(Y)) have a meaning as is, and you are changing this.

Ah, I see. I’ll keep that in mind as I continue on my journey.

The standard trick is to use true. as last line. Of have an editor that is smart enough. I’m not aware of such an editor

Or maybe I should switch to a better plugin :grin:, VSC-Prolog seems to be able to warn me when I forget a trailing comma.

:- module(skip,
          [ (skip)/1,
            op(1000, fy, (skip))
          ]).

skip(_).

That’s awesome! I’m definitely adding that to my codebase.

Typing is a long debated topic. It probably needs to be reopened. In the old days we had fully typed languages. Now we have typing on dynamically typed languages such as typescript for JavaScript, types for Python, etc. The Ciao system is probably most advanced here. Hopefully something simple and useful will emerge …

Python first introduced type-hints as comments as to not change the language. I think the same is possible for prolog. Though type is indeed a challenging topic.

At least, the VSC plugin, Sweep (for GNU Emacs) and the built-in Emacs clone do active syntax checking. Would be nice to have a simple command to do things such as removing the last line or swapping the last two. It might be more confusing then helpful though …

We have that as well, as, e.g.

%!   age(+Name:atom, -Age:integer) is semidet.

There is also an extension pack that creates runtime checks from this. As is though, the optional type annotation needs to be a valid Prolog term, but there is no formal type language. There are types as defined by library(error), must_be/2.

FWIW, in Sweep, we can remove the last (or any) sub-goal with C-M-k (kill-sexp), for example:

foo :-
    bar(X)-!-,
    baz(Y).

With the cursor at -!-, typing C-M-k gives:

foo :-
    bar(X).

Similarly we can transpose goals/terms with C-M-t (transpose-sexps), so we’d get:

foo :-
    baz(Y),
    bar(X).

I find this one useful also for transposing arguments, etc.

The relevant part of the Sweep manual is Term-based Editing (Sweep: SWI-Prolog Embedded in Emacs)

1 Like
Term deconstructor and alias:

my_pred(X=(Part1, Part2)) :-
  …

So, I had exactly the same experience as you and here is a tale of my thought process about this problem as a prolog beginner:

The begining of this journey is that you want to write a predicate where you manipulate both the argument and a subterm of the argument in a predicate:

my_pred(X) :-
  X = a(Y),
  % use X and Y.

But then, you learn about First Argument Indexing (FAI) and notice that by pulling the pattern matching in your head you get a lot of benefit like determinism and speed:

my_pred(a(Y) :-
  X = a(Y),
  % use X and Y.

But then, you notice the pain of having to write the term twice and think: “How nice it would be to have both convienience and speed !”. And so, begin to think of a solution.
Since FAI is so good, you don’t want to give it up. So the only solution is to do everything inside the head of a clause and come up some special syntax that will unify both the term and subterm inside the head.

my_pred(X=a(Y)) :-
  % use X and Y.

But then, you learn what Jan said:

And go full circle and come back to the first solution !

Also about type hints.

I believe the pack Jan is referring to is the mavis pack.

I also remember of a pack that did static checking of types but I can’t find it again. Was it type_check pack ?

But, in my experience, I found that type specification is a just a waste of time when you are writing highly experimental code. That time is better spent writing test cases with the excellent unit test pack :slight_smile:

One thing I often wonder is: what is the canonical way to define new type with swi-prolog ?
Is it by using has_type/2 ?
I could not find documentation on the swi-prolog website about this topic and only could find the typedef pack.

By the way, as a prolog beginner, it was an eye opening experience to browse all swi-prolog packs.
There are some wonderful gems in there like the clpBNR pack.

I believe the pack Jan is referring to is the mavis pack.

This looks amazing! I’ll check that out later.

But, in my experience, I found that type specification is a just a waste of time when you are writing highly experimental code. That time is better spent writing test cases with the excellent unit test pack :slight_smile:

Right, at the beginning of any project types are kinda useless indeed. However when I settle down on a few data structures and I wanna be consistent across multiple predicates it is nice to know that I’m unpacking (BeforePoint, AfterPoint) and not (CoordinateX, CoordinateY).

Maybe I should give up on using tuples and move on to use predicates to describe my arguments to a predicate.

% How I'm doing right now
my_pred((BeforePoint, AfterPoint)):-
   ...

% How I should be doing (?)
movement(Before, After):-
  ...

my_pred(Movement):-
  Movement=movement(Before, After),
  ...

hum, is that a comma list ?
I believe there is a consensus that comma list are bad.
As for the reason why, I believe it is because it is confusing with other prolog construct like compound terms.
Your example seems well suited with pairs, which the functor -/2 is commonly used:

Movement = Before-After.

But if you have different kind of pairs like movement pairs and coordinate pairs, you should totally use compound terms like movement(Before, After) and point(X, Y).

You can use the library record to facilitate the use of such compound, although it is kind of useless for nested compounds…

1 Like

By the way, I fully aggree with you.
However, have you ever experienced the situation where you actually made the mistake of calling a predicate that you wrote (even a long time ago) with the wrong type of arguments ?

I think I have never experienced this, although maybe it is because I don’t have many types in my own project ?

However, have you ever experienced the situation where you actually made the mistake of calling a predicate that you wrote (even a long time ago) with the wrong type of arguments ?

Only every single day. :smiling_face_with_tear:

Yeah I was reluctant to change the entire code base but seems like swi is finally had enough of me with my comma lists

?- my_pred(_, ((1, a), (2, b))).
   Call: (10) simple_pawn_capture_movement(_, ((1, a), 2, b)) ? abort

Notice how the last nested comma list is unpacked on the trace. No idea why that it happening, but regardless I’ll change it to compound terms.

Well, to debug these kind of error, you can use write_canonical/1:

129 ?- write_canonical(my_pred(_, ((1, a), (2, b)))).
my_pred(_,','(','(1,a),','(2,b)))
true.

130 ?- write_canonical(my_pred(_, ((1, a), 2, b))).
my_pred(_,','(','(1,a),','(2,b)))
true.

I believe this is kind of a textbook example of why the use of comma list is discouraged ^^

I am not a human, I am the tuple warning bot.

Please look at this post for an example why comma lists are not tuples.

2 Likes

So I see my tuple warning bot already engaged with you :slight_smile: Now it is me writing this.

And then you give two possible solutions:

what you could be doing instead is:

my_pred(movement(Before, After)) :- ...

A side note: in this example, my_pred/1 is indeed a predicate but movement/2 is a compound term ie a data structure. This is how I understand the statement “Prolog is a homoiconic language”: data and program are not different in their syntax. Your data can become program if interpreted as such, and interpreting a data structure as program is possible and easy.

And Prolog will let you implement types, in a way. The biggest issue is that the typing you can implement with Prolog easily is mostly run time.

Your own example from above already demonstrates how you can specialize an algorithm for a specific data structure by using a compound term with its Name/Arity. If you define your predicate only for an argument of type movement/2 and you give it a point/2, it will just fail at run time.

In the same vein, if you implement some algorithm for integers, and you use a built-in like between/3 in its definition, you will get a run-time exception. Like this:

%% some made up implementation
prime(P) :-
    between(2, infinity, P), % this will throw if you
                             % call prime(foo) and fail
                             % if you call prime(-1)
    ...

In this example the concepts of data type and value domains get conflated a bit. There is also must_be/2 which can help you turn failures into exceptions but it depends on the specific use case.

Another limitation is that it isn’t immediately obvious how to model “is a” relationships (inheritance in OOP). For example both point(X, Y) and movement(Before, After) are pairs; but how would you define a predicate that works on either, or just any pair? There are many ways to solve this depending on the specifics. Built-in predicates like sort/4 are generic in that way: if you wanted to do a stable ascending sort on the second argument of a compound term, you could write:

?- sort(2, @=<, [v(x, y), foo(1,2), bar(0,1,2), a-b], Sorted).
Sorted = [bar(0, 1, 2), foo(1, 2), a-b, v(x, y)].

The advantage of resolving the type at run time is that you can implement polymorphism as in OOP or generic algorithms without having to fight the type system or templating facilities. But again, your program will fail or throw at run time.

Finally, Prolog does have some notion of types (atoms, numbers, rationals, floats, strings, and so on) but all the considerations from above still apply.

Sorry for the rambling post, I am a programmer and don’t even have formal computer science or math education. My use of terminology is hopefully not too rage-inducing.

PS: if we talk about types it is probably worth pointing towards Logtalk. I haven’t used it but I am under the impression that it supports compile time typing somehow. Hopefully someone knows better and can give further details.

1 Like

An uninformed opinion on the topic is that a useful/usable type system for Prolog will have to come together with limitations to the the expressiveness of Prolog to a point where it would have to be recognized as a separate language. As long as that language can be compiled to target a Prolog VM this shouldn’t be such a terrible thing. I wish I had the aptitude and time to work on such an interesting project…

That seems to be the necessary consequence of a rigid type system, i.e., a type system that allow full compile time checking of all types. I think Prolog needs a type system that first allows defining types and documenting types. Next, this may be used to derive type errors without trying to proof full type correctness and it can be used to allow automatic instrumentation of the code. But, types are complex in Prolog. We also have the notion of instantiation and (closely related) modes and determinism. This requires both the notion of a type (e.g., list) as well as a term that is compatible with a list, in this case sometimes called a partial list.

The Ciao team did a lot of work on this. I always had the, possibly not so well informed opinion, the assertion language is too complicated. Possibly it is not so bad (experiences?), possibly it is possible to find something more lightweight.

1 Like

Where on the Ciao site should I start reading about this? Is the chapter on Assertions and auto-documentation?

Slight correction: “your program may fail or throw at run time if you have a type error.” (I changed “will” to “may” because instead of failing or throwing an error, it might compute the wrong thing).

People often confuse “strong or weak typing” with “static or dynamic typing”. Prolog has strong typing, as you’ll find out by executing the goal X is foo+1 – it won’t let you add an atom and an integer. But it doesn’t have static type checking, such as in C. (C/C++, incidentally has somewhat weak typing, for example union and cast let you subvert the type system in ways that you can’t in Prolog.)

1 Like