Nevertheless seems to have been abandoned in favour of SWI prolog and GNU toolchain.
Not sure about GUIs; I would consider that a very different expertise. We used our own canvas C++ rendering engine and some horrible integrated 4GL devtool call "4D", or "Fourth Dimension".
https://www.tomshw.it/data/images/2/0/4/3/infor-advanced-sch...
Seconded. Probably the biggest mind-explosion type experience as a programmer in my entire life. Even something simple like building a Sudoku solver is insanely fun IMO.
Not sure if these count but:
- Gerrit's submit predicates are configured in Prolog. (Edit: deprecated!)
- An internal product I worked with at Google is built around a Datalog knowledge base that gets codegen'd. Then you can query it directly or use a UI to generate queries.
Both of these seem to work nicely but they're pretty limited use cases.
My experience with Prolog at university was that it was pretty fun but there's just no way I would wanna use it for anything that isn't inherently Prolog-shaped. As soon as we were implementing nontrivial stuff in it the abstraction leaked like a sieve and you felt mostly like you were hacking against the backtracking algorithm rather than taking any actual advantage of the language.
(I run the team that works on Gerrit at Google.)
IIRC, The result of a failing Prolog rule was a single string, which would surfaced to the user. It was often impossible for users to understand why that condition triggered and what they could do to address the issue.
On a technical level the idea was also flawed. The Prolog interpreter had to be constrained (to avoid infinite loops), with hardcoded recursion limit. This meant that adding one Prolog rule too many could trigger a failure in the interpreter which would then throw 5xx errors and wedge the server.
Oh well that sounds like bad coding, definitely. You may run into that sort of problem -infinite recursions- the first few times you code in Prolog but you learn to avoid it, just as you learn to avoid infinite do-while loops etc.
Btw, since you mention the server being wedged, a few years ago I was working at a C# shop and there was an incident where _all_ our deployments died because of an infinite iteration. Some code was missing its exit condition from the loop and since this was on our platform which had the common functionality, it affected all our clients.
There was a meeting upstairs, inevitably. When the development team head returned from the meeting he told us that he had explained what happened and the upstairs had decided that we should no longer use iteration because it risked going haywire, as we had just seen. The alternative? Use recursion instead. In C#. Obviously nobody ever complied with this stupid idea.
But you can bring down a bunch of servers very easily in any language you like. Another time, in the same company, there was an infinite recursion now, created in the web templating language we used, an in-house version of django. That was a bit unexpected. In another job, I got some gentle ribbing from colleagues for sucking up all the juice from the database servers with an inexpertly written SQL script. And so on.
I mean, people say sometimes that all that stuff about the halting problem is purely theoretical and so on, but then non-termination bites you in the boogies and you're left with egg on your practically-minded, results-oriented face.
But almost all of the people configuring Gerrit submit predicates are doing so as their first and only Prolog experience. They are not the authors of Gerrit or even the owners of the server instance.
To make yet another analogy, I know almost no C++. If you asked me to write some C++ templates to configure your system, I'd probably make a very big mess. Despite the people saying that they're a bit like unification and so on. I just don't know how to use them. So don't ask me to. And don't ask your users who don't know Prolog to use it. That's a bad project management decision.
Also, friendly advice: configuration predicates in Prolog should _never_ _ever_ _ever_ be "rules"; only "facts". That way you don't risk infinite recursions- or run much risk besides. The same goes for any other language I think: don't let the configuration execute arbitrary code or you're in for a surprise, inevitably.
"Nobody knows or likes Prolog" is exactly the reason why I refrain from using Prolog in professional environment. I can use it, I like it, but unless I have full team of people committed it's an empty effort (even harmful).
So yeah, if smart people failed to convince other smart people, there's something wrong with it.
And in my view is people don't get to like it because current sources aren't good enough. There is documentation but it's not clear what the target solution scope is (and rarely people professionally implement Sudoku solvers), what the benefits are and what are the success stories.
Maybe those weren't very smart people, after all? I mean we only have Google's assurances to go by, that they are the place where brilliant people go to feel mediocre etc, but doesn't that sound like a pick-up line, that would put off most people who don't consider themselves dumb?
Also: a good engineer never blames her tools.
All this to say that I've worked on a few substantial, non-trivial software projects in Prolog and I can say with confidence that "the abstraction" does not "leak like a sieve", like you say. It's bad programming practices that leak like a sieve. You can get in a real mess in Prolog if you are not careful. But you can get in a real mess with OOP if you're not careful, as e.g. in the object-relational impedance mismatch.
One of the projects I worked on in one of the two jobs I mention above was indeed a complete mess. But not because of Prolog: because of people who copy/pasted code left-and-right and puked spaghetti on the source file completely oblivious of the need to, one day, maintain their mess. Again, nothing that Prolog is particularly guilty of.
Remember: programming languages don't kill projects; programmers kill projects.
_________
[1] I've become like the proverbial FORTRAN programmer: when I tried coding in Python it came out all Prolog-like. Bad idea. Python doesn't have tail-call optimisation by default.
The advice in this technical report is sound:
Coding Guidelines for Prolog
https://arxiv.org/abs/0911.2899
It goes over some style matters but also makes more practical recommendations about using the language for best results. I have followed those guidelines in the last few years, often before reading the book, and they have generally served me well.
My own, very personal advice is to avoid the dynamic database as if it was the plague. To clarify, the "dynamic database" refers to the use of assert/1, retract/1 and friends to add to, and remove clauses from the program database. Prolog programs are stored in a relational database and since we can open it up, write new program clauses to it, and compile it again, we call it the "dynamic database". Well, it may be dynamic, but it is also evil. Every time I use it, I end up hurting myself. So I've learned to hurt myself before I ever use it, just because that saves me most of the later pain.
In my current project I found myself going to great lengths to avoid using the dynamic database. For example, I have some code that auto-generates predicates to access the elements of a list that represents the state of the world as perceived by an autonomous agent guiding a robot. These "access predicates" look like this:
inspect(map_area,map_area(X_min,X_max,Y_min,Y_max),[map_area(X_min,X_max,Y_min,Y_max),_,_,_,_,_]).
inspect(scan_area,scan_area(X_min,X_max,Y_min,Y_max),[_,scan_area(X_min,X_max,Y_min,Y_max),_,_,_,_]).
inspect(position,position(X,Y,Theta),[_,_,position(X,Y,Theta),_,_,_]).
inspect(observations,observations(Contacts,Count),[_,_,_,_,observations(Contacts,Count),_]).
inspect(grouped_observations,grouped_observations(Groups,Count),[_,_,_,_,_,grouped_observations(Groups,Count)]).
% etc.
Here, the list that represents the state-vector is the last argument of inspect/3 and its terms can be inspected by passing an initialised state-vector list to that argument. For example: ?- initialised_state_vector(_Vs), inspect(position,P,_Vs).
P = position(x(0.0), y(0.0), theta(0.0)).
Modification operations work in a similar way: modify(position,increment,x,[Map_area,Scan_area,position(X,Y,Theta),Scan_params,Observations,Grouped_observations],
[Map_area,Scan_area,position(X_,Y,Theta),Scan_params,Observations,Grouped_observations]) :-
increment_float(X,X_).
modify(position,decrement,x,[Map_area,Scan_area,position(X,Y,Theta),Scan_params,Observations,Grouped_observations],
[Map_area,Scan_area,position(X_,Y,Theta),Scan_params,Observations,Grouped_observations]) :-
decrement_float(X,X_).
% etc.
And then I can do: ?- initialised_state_vector(_Vs1), modify(position,increment,x,_Vs1,_Vs2), inspect(position,P,_Vs2).
P = position(x(0.1), y(0.0), theta(0.0)) ;
false.
All this is to avoid writing the terms in the state-vector (like position(X,Y,Theta)) to the dynamic database, then using asserts and retracts to modify them, which is the obvious thing to do. What I did above is also a more efficient way to access the elements of the state-vector list by unification, instead of having to scan the list to find a term (which can add up to a lot of scanning in the project I'm working on). Using asserts is also costly (but clauses can be accessed fast once asserted).The hitch is that some of the terms in the state-vector above have arguments that are themselves lists and those lists need to be accessed to inspect or modify them. Which means scanning a list again. So I added clauses of inspect predicates that look like this:
inspect(group,GroupId,group(GroupId,Contacts,Count),[group(GroupId,Contacts,Count)|_]).
inspect(group,GroupId,group(GroupId,Contacts,Count),[_,group(GroupId,Contacts,Count)|_]).
inspect(group,GroupId,group(GroupId,Contacts,Count),[_,_,group(GroupId,Contacts,Count)|_]).
inspect(group,GroupId,group(GroupId,Contacts,Count),[_,_,_,group(GroupId,Contacts,Count)|_]).
inspect(group,GroupId,group(GroupId,Contacts,Count),[_,_,_,_,group(GroupId,Contacts,Count)|_]).
So that I can access the elements of a list-argument at constant time. So that's effectively compiling a Prolog list to act as an array, or a dictionary I guess. I don't know how big the "compiled" lists have to be, so I set a configurable "budget" before I generate the access predicates above and hope for the best. An alternative would be to simply add more inspect clauses for list-arguments as needed by using asserts but that's only going to happen over my cold, dead body.All this could have been done much more simply if I used assert/retract, and also used Prolog lists as they're intended to be used, as flexible linear-time access structures. But if I did that, I'd end up in a world of pain later on. I know that from experience and I have the scars to show for it.
I suppose my approach could be seen as jumping through hoops to do something that is easy in other languages - just write some stuff to a database. But, I've worked with OOP languages on backends accessing relational databases all the time I was in the industry, and it's anything but easy. It's a constant source of pain instead: somehow every OOP textbook I've ever read forgets to tell you this, but OOP languages are simply not good at handling data. In Prolog at least my backend and my database are one and the same, my code _is_ my database, and I can has a clean and tidy representation where my entire program's state is in the source code (rather than spread out between the source and the database, and between the source and the runtime). So it takes a bit of work. And what doesn't?
My scheme above also serves to preserve the "logical purity" of Prolog, since accessing the state-vector is side effects-free, but that was really not my motivation. I don't care about purity. However, in Prolog, when you leave the declarative, logical, immutable paradigm behind and enter the world of destructive data structure updates, you enter a world of pain. The dynamic database is evil.
OK, so this got a bit too long, by far. You asked for a "hint" and I had to go and write a little white paper as usual. But I hope all that is useful to you, as a kind of case study.
I adore the language (with all its good and bad parts) and I always admire people who work with it professionally and try to learn from them since my professional career will not allow me to get any real world experience with Prolog. You helped with that.
(Hint: the error came from an Apache lib, just like in this link: https://stackoverflow.com/questions/3030903/content-is-not-a...)
I managed to actually solve all the programming tasks we were given (and as I was the only one to do so among my peers, many copied my solutions) - but if anyone then asked me to explain how I did it, I was at a loss. I basically stared at the screen until some sort of intuition hit, and if that didn't work, repeat the process until it does...
I guess the paradigm never really "kicked in" for me ;)
It took me years for Prolog to kick in and it did when I was trying to... optimize my taxes in a very complex context (e.g. where I live there's a VAT difference on ketchup depending if it's sold on the fresh hot-dog or wrapped one).
But since then I try to advocate and use it as often as I can. Mostly because paradigm is super fun to work with and refreshing after all day of working on "normal" languages.
Then, say 5 years later, it "kicked in" and I kept wondering "why isn't this implemented in prolog?" at many, many points.
Let's say you have a Prolog optimization app for your business domain, such as an elaborate version of the container ship scheduling problem discussed on the site [2]. Traditionally you'd generate Prolog facts encoding the problem instance, and then invoke Prolog to solve the problem and output one or more found solutions. A variant of this expecting a certain directory structure with facts/rules to exist is used by the Aleph machine learning package also demoed on the site [3]. This is popular and very flexible because of Prolog's ultra-flexible term datatypes.
Alternatively, you can implement, in Prolog itself, a small wrapper to parse a JSON representation of your problem instance, where "implement" doesn't really describe it since basic JSON parsing using Prolog's built-in operator precedence parser is as easy as
:- op(600, xfy, :).
MyJSON =
{ "this": "bla",
"that": 100,
"L": [ 1, "two", 3 ] }
but applied to standard input using term reading, provided your Prolog has curly braces (should come with ISO DCGs). The in-browser variant of Quantum Prolog [4] emits JSON right away.An API/language binding to call Prolog from a particular host environment is less suited for the kind of apps discussed on the Quantum Prolog site since these tend to be longer-running, and potentially parallelized workloads. But let's say a multiplayer game universe represented as Prolog database (or maybe a highly interactive solution viewer/picker under very particular circumstances) might need a different integration model.
[1]: https://quantumprolog.sgml.io/
[2]: https://quantumprolog.sgml.io/container-planning-demo/part1....
[3]: https://quantumprolog.sgml.io/bioinformatics-demo/part1.html
[4]: https://quantumprolog.sgml.io/browser-demo/browser-demo.html
There is indeed an issue with side-effects that doesn't work well with the backtracking semantics. And I am not sure there's a good solution for it. Other LP languages, like Curry, just forbid side-effects under nondeterminism and you have to collect and handle them using an implementation of effect-handlers if they aren't at the top level. That's a quite restrictive solution that needs a lot of discipline, a good architecture, and framework support.
But Prolog, doing side-effects whenever, wherever such a side-effect-entailing predicate is encountered as a proof goal doesn't work either and you're bound to make your life very difficult. Even if you are careful, the code that you produce, just looks like imperative code. So nothing really gained here from using Prolog.
I'm still looking/researching about solutions inbetween. Without too too much success.
[1]: https://quantumprolog.sgml.io/container-planning-demo/part2....
That makes interoperability inherently imperative.
My use case is, that you usually wouldn't un-move a robot. You want to re-plan from the current position, if an obstacle comes up. You sure as hell don't want to backtrack into an alternate plan from your original starting position.
That's right, but I think if you backtracked that far, the planning should simply fail.
Are we working on something similar? I'm using Inductive Logic Programming to learn robot plans. I don't know too much about planning but I reckon what we're learning are generalised plans (with an option to make them as specialised or general as needed).
The project I'm on has started only this June so I'm not yet at the point where there's need to change a plan, but I've done some thinking about it. I might be over-optimistic, but I think it's a matter of putting plans on a stack and allowing them to be carried out in LIFO order. Then, if the original plan (the first to be put on the stack) doesn't work anymore, you just backtrack to make another plan from the current position, after checking the current state of the world.
OK, I don't know if that makes any sense to you. Does it?
It's not the first time us two have this kind of discussion.
> Then, if the original plan (the first to be put on the stack) doesn't work anymore, you just backtrack to make another plan from the current position, after checking the current state of the world.
Sure. That's the replanning. But actually you could backtrack into a plan you've already got that has the same prefix as the plan you've already chosen. Presumably a different plan differs from exactly this position and avoids the obstacle that made the current plan fail at that point. You'd have to keep track of the movements you've done. But as the plans are generated lazily, both concepts ('replanning' and 'choosing one of the original plans that hasn't been "superseded by reality" yet') might actually be the same.
Oh, I think I remember us talking about ILP. Sorry, I didn't recognise your username. Without avatars on HN I find it hard to remember who I've interacted with. I don't think we had talked about robotics or planning though.
Anyway now I'll remember you :)
>> Sure. That's the replanning. But actually you could backtrack into a plan you've already got that has the same prefix as the plan you've already chosen. Presumably a different plan differs from exactly this position and avoids the obstacle that made the current plan fail at that point. You'd have to keep track of the movements you've done. But as the plans are generated lazily, both concepts ('replanning' and 'choosing one of the original plans that hasn't been "superseded by reality" yet') might actually be the same.
I think I see what you mean. There's certainly a problem with backtracking all the way to the point where a plan failed only to pick up again from that point- and probably fail again. I don't think I can reason about this from first principles. I'd have to try it out and see what works and what doesn't.
Unfortunately I get this weird vibe with many Common Lisp libraries that they feel... legacy and deprecated. For sure CL isn't super popular language and it is very stable, but minimal development dynamic makes me feel insecure about the solution.
This doesn't directly answer you, but Prolog was used by David Hovell the Windows NT network configuration to enforce consistency [1][2].
I've heard it's been removed since, so doesn't count as "modern". It's a _beautiful_ application of Prolog though.
[1] https://news.ycombinator.com/item?id=36821871 which leads to https://web.archive.org/web/20030218034509/http://www.resear... -- very worth reading! [2] https://news.ycombinator.com/item?id=14046420
> Racklog is an embedding of Prolog-style logic programming in Racket. “Embedding” means you don’t lose Racket: You can use Prolog-style and conventional Racket code fragments alongside each other. Racklog contains the full repertoire of Prolog features, including meta-logical and second-order (“set”) predicates, leaving out only those features that could more easily and more efficiently be done with Racket subexpressions.
https://docs.racket-lang.org/racklog/index.html
Racket also has GUI, and basically everything else you may need.
How about good docs and examples, community, decent perf for small apps at least?
Racket newbie here. Had just played around with a little some years back when it was called MZ Scheme, PLT Scheme or such. There was an IDE called Dr Scheme, IIRC. I guess all that is part of Racket now.
The performance is around Python / general scripting level; perfectly acceptable.
As you guessed, DrScheme became DrRacket.
You can find more info at https://www.racket-lang.org, presented much better than I could do here.
Performance: closer to Groovy, Clojure or Raku than to Python, Ruby or JS. That is, startup is not as fast, but execution after that is decent. For a few years, performance improvements were not the focus of development due to the migration from custom VM to Chez Scheme. Now Racket on Chez is the default and I think we'll see further improvements on that front (easier maintenance and better potential performance were the reasons behind the migration IIRC.)
The community is small, and the core strength of Racket is not something normal programmers use in day to day programming (that strength being the ability to shape the syntax and semantics of your code at will, ad hoc, anytime, with no friction at all, in any way you want). On the other hand, the results of giving that power to a bunch of very smart people are really worth seeing.
I say the same thing about CUE these days, it can be a real mind bender but gives us a new way to think about taming config and schema complexity. CUE is also much easier to add into existing workflows to turn that logical programming glory into pragmatism.
The language is going to a foundation (still waiting for update from the CUE team), fun fact, it started as a fork of Go (for the lexer, parser, and a bunch of goodies in that source tree)
CUE's history is from borg, borgfig, GCL, but takes the approach Marcel always wanted to if not tied to the existing practices in a large company, namely Typed Feature Structures and logical programming (no overrides, layering, inheritance, general recursion)
We're about to release a TUI for working with CUE, and hoping that it will make learning much easier for people. We do a lot with Cuetorials, but there is only so much you can do on a website (/rant WASM limitations). Using the Go SDK is the way to unlock CUE super powers.
But I don't know if they still support prolog (or maybe as a plugin provided by them for the migration)
Prince's [3] base is Mercury and integrates Rust and C without fuss.
[1] https://mercurylang.org/ [2] https://mercury-in.space/crash.html [3] https://www.princexml.com/
https://terminusdb.com/ is almost entirely written in prolog. + a little bit of Rust and JS.
E.g. in Prolog you don't usually operate on collection but instead provide information what the collection item actually is and then let the implementation figure it out. Drawing dependency tree is an effect of 3 rules: what is an entity, how to figure out that entity X and Y have relation, how to represent it.
I have 2 gripes with it, though. One is that it's difficult and thus it's hard to convince people to learn/use it. Second - I didn't yet found way to seamlessly integrate it into other apps (I know that FFI is an option, but it doesn't convince me as production ready).
For the last couple months I've been looking for some more "production" Prolog alternatives but what I found so far (LispWorks, Franz Allegro CL) costs a kidney and is not integration friendly.
If anyone has success integrating Prolog in modern software please share, I'm really curious. I'm also looking for some GUI solutions in it as XPCE doesn't work on Mac :(
Oh, and check out the Swish for online notebook: https://swish.swi-prolog.org