Ask HN: What are some interesting examples of Prolog?

198 points by hazbo ↗ HN
I'm trying to source some programs and libraries written in Prolog to gain a better understanding of how a complete application is put together. Lots of Hello, World-esque examples out there, but looking for things that are a little less trivial.

I did come across terminusdb: https://github.com/terminusdb/terminusdb which looks interesting.

Any other codebases people would recommend that are worth a read?

63 comments

[ 3.9 ms ] story [ 139 ms ] thread
Not exactly a big codebase, but it was a revelation for me how natural typecheckers can feel in Prolog: I basically rewrote typing rules with some tweaks: [1]

Also, tests were surprisingly enjoyable in Prolog: [2].

[1] https://github.com/EarlGray/language-incubator/blob/29755c32... [2] https://github.com/EarlGray/language-incubator/blob/29755c32...

Not a criticism of the code here by any stretch, I always find it impressive when people manage to understand languages that are totally foreign to me, that being said however Prolog has always struck me as uniquely unreadable. Even back in university when we looked briefly at different styles of programming, the code always ended up looking something like lines 107-132 in your example, which to someone jumping in is pretty much indecipherable.

Is anyone aware of any higher level abstractions for logic based languages that is actually used in industry? From what I’ve seen in the various banks and fintech groups I’ve worked with, prolog itself was very rarely if ever used ‘in anger’, but I have heard of it being used for automation so I’m very curious what that looks like.

Regarding the code in question, it is possible to write it a bit more elegantly and more compactly, with a combination of techniques. For example, let's consider this snippet from lines 110 to 119:

    type(Ctx, case(T0, {z, Tz}, {s(V), Ts}), Ty) :- !,
      isvar(V),
      type(Ctx, T0, nat),
      type(Ctx, Tz, Ty),
      CtxS = [{V, nat} | Ctx], type(CtxS, Ts, Ty).
    type(Ctx, case(T0, {inl(Vl), Tl}, {inr(Vr), Tr}), Ty) :- !,
      isvar(Vl), isvar(Vr),
      type(Ctx, T0, uni(Ty1, Ty2)),
      Ctx1 = [{Vl, Ty1} | Ctx], type(Ctx1, Tl, Ty),
      Ctx2 = [{Vr, Ty2} | Ctx], type(Ctx2, Tr, Ty).
It is clear that the two clauses are mutually exclusive if the second argument is known, because they differ in various terms already in the head. For instance, {z, Tz} is clearly different from {inl(Vl), Tl}. A Prolog system with deep indexing, which is also used by Mercury, can detect this, and in such a system, we can therefore remove the two occurrences of !/0 in the code, yielding a more general and shorter version:

    type(Ctx, case(T0, {z, Tz}, {s(V), Ts}), Ty) :-
      isvar(V),
      type(Ctx, T0, nat),
      type(Ctx, Tz, Ty),
      CtxS = [{V, nat} | Ctx], type(CtxS, Ts, Ty).
    type(Ctx, case(T0, {inl(Vl), Tl}, {inr(Vr), Tr}), Ty) :-
      isvar(Vl), isvar(Vr),
      type(Ctx, T0, uni(Ty1, Ty2)),
      Ctx1 = [{Vl, Ty1} | Ctx], type(Ctx1, Tl, Ty),
      Ctx2 = [{Vr, Ty2} | Ctx], type(Ctx2, Tr, Ty).
The isvar/1 tests can be removed if we use a better representation of our data. For instance, if we use the wrapper v/1 to denote variables in the data, we can replace these clauses by:

    type(Ctx, case(T0, {z, Tz}, {s(v(V)), Ts}), Ty) :-
      type(Ctx, T0, nat),
      type(Ctx, Tz, Ty),
      CtxS = [{V, nat} | Ctx], type(CtxS, Ts, Ty).
    type(Ctx, case(T0, {inl(v(Vl)), Tl}, {inr(v(Vr)), Tr}), Ty) :-
      type(Ctx, T0, uni(Ty1, Ty2)),
      Ctx1 = [{Vl, Ty1} | Ctx], type(Ctx1, Tl, Ty),
      Ctx2 = [{Vr, Ty2} | Ctx], type(Ctx2, Tr, Ty).
Finally, the variables CtxS, Ctx1 and Ctx2 are only used once each, so we can simply write:

    type(Ctx, case(T0, {z, Tz}, {s(v(V)), Ts}), Ty) :-
      type(Ctx, T0, nat),
      type(Ctx, Tz, Ty),
      type([{V, nat} | Ctx], Ts, Ty).
    type(Ctx, case(T0, {inl(v(Vl)), Tl}, {inr(v(Vr)), Tr}), Ty) :-
      type(Ctx, T0, uni(Ty1, Ty2)),
      type([{Vl, Ty1} | Ctx], Tl, Ty),
      type([{Vr, Ty2} | Ctx], Tr, Ty).
With the necessary provisions in place, it may be possible to shorten it further, by generating such code automatically from more declarative descriptions, using a mechanism called term expansion which is available in most Prolog systems. It is analogous to what other languages such as Rust call macros.
Prolog was really friendly to creating little DSLs.

I used to be a specialist in a product called Tivoli Enterprise Console. Which was a late 90s/2000s era event correlation system that used an ancient prolog dialect as its rules engine.

I had an awesome boss who let me do whatever, a limited set of tools, and a partner in crime who complemented my style. So we ended up implementing an Oracle database interface in Perl and a client application that allowed others to add facts to the database. Periodically, we’d load the facts into the prolog system and could make queries to tell us what apps were impacted, did we care about them, who was responsible, etc.

The end system was able to identify app owners, system administrators and other information on any of about 20k servers and lots of network gear. We were able to take a flow of about 300k daily events and cook it down to about 40-50 actionable alerts and a few hundred automated actions.

The whole thing took about 3 months to build and another 4-6 to tweak. We got promoted and moved on, but the system hummed along for about 4-5 years until IBM started killing the underlying product. I think the place pivoted that functionality to ServiceNow.

That's the largest piece of Prolog code I've ever written, so criticism is warranted and I'm glad to learn how to improve it. I still wanted to share it to show how typing rules translated almost directly to Prolog clauses.

I agree that this piece of code was written in a more academic mindset, with large dose of TAPL-specific jargon and abbreviations which can easily throw anybody off if not known beforehand. I do not have good examples of industrial-grade Prolog code bases, if that's what was asked.

Worth checking out if you're interested in using constraint/logic programming languages for type checking is the Statix (https://www.spoofax.dev/references/statix/) language, which is used in the Spoofax language workbench. It works similarly to Prolog in solving constraints, but adds support for scope graphs, which are a generic approach to specifying the name binding behavior of programming languages through logic constraints.
One of the most interesting applications of Prolog I have seen in the recent past is the formalization of dose-escalation trial designs that occur in clinical oncology. In particular, David C. Norris has implemented a Prolog formulation of the cumulative cohort design (CCD) as part of his precautionary package:

https://github.com/dcnorris/precautionary/blob/main/exec/pro...

This Prolog program can be used to exhaustively enumerate all possible arising cases, and also to complete partially given trials. In addition to the specific usage mode of telling the clinician what action to perform next after a sequence of events has occurred, it is also possible to ask interesting questions about the trial design as a whole, such as whether specific cases can arise at all, or whether a specific instance was performed according to the protocol. In this sense, the formulation truly serves as an executable specification of trial designs that are otherwise stated only comparatively informally in the medical literature, and may even be subject to divergent interpretations. The formulation uses Scryer Prolog and the latest Prolog language constructs (such as if_/3 and CLP(ℤ) constraints) to achieve a short and very general description. Such declarative specifications may help considerably to improve safety and efficiency of clinical trials, by making all steps and outcomes amenable to analysis and comparison.

Theorem provers and reasoning engines are also often implemented in Prolog. A recent example is solidarity by Jos De Roo:

https://github.com/josd/solidarity

Prolog is also frequently used for prototyping interpreters. For example, Adrián Arroyo Calle is working on a MIPS simulator written in Prolog:

https://github.com/aarroyoc/mipsie

Simon Forman has implemented a Prolog interpreter of Joy in which, remarkably, the declarative description also serves as a type inferencer and type checker:

https://git.sr.ht/~sforman/Thun/tree/master/item/source/thun...

Porting this code to Scryer Prolog could be an interesting project if it appeals to you. There is already an issue for it and pertaining discussion:

https://github.com/mthom/scryer-prolog/issues/388

Wow this is the best thing since the Actually Portable Executable source code. https://justine.lol/thun.png How did the author generate those letters?
Mercury (https://mercurylang.org) is a language descended from Prolog that has some interesting examples. Personally I think it shows a lot more promise than many other languages people get very excited about.
Norvig’s AI book has a lot of prolog in it.
I want to believe in prolog. I'm having trouble, like pg saying the definition of append is really really good and it's all downhill from there. Also Jet Propulsion Laboratory has a prolog dialect that's non-turing complete for its inspection of its internal systems.

prolog looks like a third way, not C and not Lisp.

You could look at "PRESS: PRolog Equation Solving System", "a system for solving symbolic, transcendental, non-differential equations."

    Journal of Symbolic Computation
    Volume 7, Issue 1, January 1989, Pages 71-84
    "Solving symbolic equations with PRESS"
https://www.sciencedirect.com/science/article/pii/S074771718...

Source: https://github.com/maths/PRESS

- - - -

BTW, Does anyone know where I can find the source for MIXTUS partial evaluation system?

The author has a linkedin account in Sweden, I would message them directly.
Tack så mycket! (That's Swedish for "Thank you very much." :)
Make files are prolog programs in drag.
That's an interesting take. I learned Prolog at university but I never thought of it this way.
Not a lot of code but a somewhat different use of Prolog than you're likely to see elsewhere. I used my fork of a MQTT library for Prolog (https://github.com/sprior/swi-mqtt-pack) to implement the central controller for my home automation system. The system responds to MQTT events and then coordinates the appropriate action by sending MQTT messages to other home services. Recent versions of SWI-Prolog also support redis and I've started using that to store device configuration and state between services. The MQTT version is actually a reimplementation of my previous version which used CORBA for inter-service communication.

I don't distribute the home automation code however it's pretty specific to my house. The MQTT library provides some building block examples.

You can also see some other comments I've made on HN which describe some other details.

That is cool! How compact is code that sets groups of behavior? Can you paste any examples? This seems like one of the really powerful aspects of Prolog. That we can work on the microscopic level, and the computer can prove correctness or find solutions to competing constraints.
Here's part of a sample sequence: 1) Smarthome gets a notification of a vehicle detected in the driveway 2) sends a request to get an image from the driveway camera 3) gets the image, sends it off for storage and analysis 4) sends it to an image recognition service which returns a description(make/model/color + other info) as JSON 5) receives analysis, matches it to known vehicles and announces the results

The send_display_vehicle_id(Id) in the end actually does a lot, it queries redis for previously announced display type devices, then looks up the capabilities (bit depth, resolution) of each device and formats accordingly then sends the display request for each. Also send_android_notification sends the request to some Java code that initiates a push message to my phone and watch.

None of this is rocket science in terms of Prolog message(MQTTPathAsList, MessageBody) is my general predicate for handling an incoming MQTT message once I've subscribed to it and it's convenient to deal with a MQTT topic path as a list.

message(['Smarthome','Notify', 'vehicle','motion'],_):- log('brain saw vehicle motion'), get_short_timestamp(S), atomic_list_concat([S, ' Vehicle Motion'], NotificationMessage), send_android_notification('Smarthome', NotificationMessage), request_image('alibi4').

request_image(Camera) :- publish(['Smarthome', 'Video', Camera, 'brain', 'request']).

message(['Smarthome','Video', _ ,'brain','response'],Image):- store_image(Image, 'brain'), analyze_image(Image, 'brain').

analyze_image(Image, Id) :- publish(['Smarthome', 'Sighthound', 'analyze', Id], Image).

message(['Smarthome','Vehicle', 'Sighthound'],VehicleJson):- vehicle_id(VehicleJson, Id), get_short_timestamp(S), atomic_list_concat([S, ' brain saw vehicle: ', Id], NotificationMessage), send_android_notification('vehicle', NotificationMessage), send_display_vehicle_id(Id), atom_concat(Id, VehicleJson, Description), atom_concat('brain saw vehicle: ', Description, Message), log(Message).

I used Prolog at my job to fill out complex regulatory forms required in the financial industry. I have a Perl module that queries an Oracle database and constructs the facts database. This is paired with a set of rules to write out the form elements.

I have one other Prolog project that is part of a tool. It explains the relationship between potentially related people. It is used in financial surveillance.

I use it for reverse engineering unknown struct layouts for DWG classes. A field packing problem.

I'm using picat, a better Prolog dialect, and generate the facts automatically from C to generate the field layouts via picat automatically.

https://github.com/LibreDWG/libredwg/blob/master/examples/AC...

optimization problems as in compilers are extremely natural in Prolog.

I've recently come across "The Eye of Horus", a paper + associated code base [1] analysing attacking transactions on smart contracts using DataLog (a subset of Prolog). Not all of it is written in DataLog, but the vulnerability analysis is - the core component of the framework.

[1] https://arxiv.org/abs/2101.06204

What’s the best way to get started with Prolog on macOS? Implementations, editors, etc?
SWI is by far the best documented of the open source versions with Ciao coming second. They both ship with dev environments, the Ciao one is an elaborate Emacs mode and the SWI one is implemented in a custom GUI toolkit.
Not sure if you'd consider this a "complete application" that is bigger than a "Hello-World-esque example" but I've worked on a Discord bot in SWI Prolog[1] for a while with a friend. It was an interesting experience and although a lot of the work (e.g. SWI has a ready-made library for interfacing with WebSockets and sending out HTTPS requests).

Some interesting applications of Prolog specifically include using predicates to filter messages by certain criteria (e.g. if it was sent by the bot's account or not), being able to hot-reload by invoking the make/0 predicate, and homoiconicity to (in theory) easily evaluate random code supplied by a user.

[1]: https://github.com/prolord-pl/prolord