Prolog is the only good programming language. I should know, my website is written in Prolog.
Unfortunately, C is the only useful programming language.
Scientists have been trying to find an answer to this problem for nearly 50 years. Some make their C more like Prolog. Others make their Prolog more like C.
I offer a new solution: simply add Prolog and C together. I call it, “C Plus Prolog”, or “C+P” for short.
:- include(stdio). int func main { puts("Hello, world!"); return 0 }.
If you’re familiar with C, you’ll notice this is some sort of weird, bad C. You’re mistaken, however. This is valid Prolog, using some non-standard features of SWI-Prolog for the curly braces.
This Prolog is read as a list of terms, and converted to valid C code. So this:
int func main(int var argc, char:ptr:array var argv)
Translates to this:
int main(int argc, char*( argv[]))
Beyond some obvious changes like var
and func
operators, which make the syntax expressible as Prolog, there are some unexpected quirks:
- Symbols starting with capital letters or underscores are for use with Prolog, and won’t translate to C unless wrapped in single quotes:
int:ptr var string = 'NULL'; '_Bool' var v = true;
- As a result of the above, character literals start with
#
:
char var c = #c; % quotes not required for lowercase letters char var c2 = #'C'; char var nl = #'n';
- Operator precedence was maintained for existing Prolog operators. For example,
=
and comparison operators like==
,<
, and>
have the same precedence, so you'll need parentheses where you wouldn't in C:
This is also why the arrow operator ->
was replaced with @
, since it's used in a completely different way in Prolog and has a completely different precedence.
C+P:
C:
- Complex type declarations are different. C-style declarations are perfectly expressible in Prolog, I just don't like them.
Becomes
It may also help you remember the difference between const char *
and char const *
:
const(char):ptr, char:const(ptr)
The examples provide a more complete picture of the syntax.
In the end, it's C but more verbose and particular about semicolons. Not exactly a silver bullet.
Let's introduce the *=>
operator.
Take this snippet from example 04:
max(A, B) *=> A > B then A else B.
I said C+P does no processing beyond translating terms to C, but it does one small step. The compiler will gather all the terms defined with *=>
, and then substitute the left-hand for the right-hand in the rest of the code until no more rules apply.
Because this operates on Prolog terms, not text, we don't need any extra parentheses in our max(A, B)
macro to prevent mishaps with operator precedence. It's inserted into code as a single term, and looks exactly like a function call in use:
float var f = max(12.3, 0) + 20; printf("Should be 32.3: %fn", f);`
It's converted to the following C:
float f=(((12.3>0) ? 12.3 : 0)+20); printf("Should be 32.3: %fn", f);
Also, I'm tired of adding n
at the end of all my printf
statements. Let's defined another macro, println
:
PrintLn *=> PrintF :- PrintLn =.. [println,Format| Args], string_concat(Format, "n", Format2), PrintF =.. [printf,Format2| Args].
We have full access to Prolog at compile time using :-
, allowing us to do just about anything.
This macro gets any instance of println(Format, Args...)
with a string literal Format
, and converts it to printf
with a newline appended to Format
.
Simple enough. Let's implement poor man's generics.
Example 05 defines a generic type, list[T]
, using the following syntax:
list[T] { struct list[T] { T:ptr:ptr var items }; % Let's also define syntax for type-specific functions, in this case “list[T]:capacity” size_t:ptr func list[T]:capacity(list[T] var this) { ... }; ... }.
This will work similarly to C++ templates.
For simplicity of implementation, we'll require the user to instantiate the template.
declare(list[int]). declare(list[const(char):ptr]).
We could probably do this automatically by scanning the code for any usage of list[T]
and instantiating the template right above it, but I'll leave that as an exercise for the reader.
We also have some syntax to get a function name with list[T]:Method
:
int func main { list[int] var my_ints = list[int]:new(17); size_t var size = *(list[int]:capacity(my_ints)); for(int var i = 0; i < size; i += 1) { list[int]:append(my_ints, i*i) }; for(int var i = 0; i < size; i+= 1) { printf("%d squared = %d.n", i, list[int]:get(my_ints, i)) }; return 0 }.
Not exactly C++, but it keeps the namespaces clear.
Let's read the macro.
It matches on our template as you might have expected:
Name[T] {Body} *=> Comment :- +ground(T), atom(Name), atom_concat('//Defined template type: ', Name, Comment),
We have a templ
10 Comments
tombert
I really need to learn Prolog. It looks interesting and powerful, but it's different enough form most languages that I'd need to actually sit down and learn it and how to write useful stuff with it.
At a superficial level, it looks a bit like Z3, which I do have some experience with, but I suspect that there's a lot of intricacies to Prolog that don't apply to Z3.
rhelz
This is the most creative idea I've seen in a long time.
It is also the smoothest way of "declaring an imperative" I've ever seen.
It is also the most amazing use of the parser which is built-in to prolog (no, not the DCG's, the other parser, the railroad/operator precedence parser).
I wish it had been discovered around 1981. The world might be very different.
ysabelbristol
[dead]
elteto
> I call it, “C Plus Prolog”, or “C+P” for short.
Missed the chance to call it CPP.
tannhaeuser
If you like this stuff, consider how to "implement" JSON in Prolog:
Basically, you don't ;) all we did here is declaring ':' as an infix operator. The next lines bind/unify a term to the var JSON, and the last line is again unification in action (basically destructuring on steroids), binding the vars X, Y, and Z to the respective value 1, "a string", and "a compound" from the previously bound JSON var.
You can paste this snippet into [1] and execute it right in your browser to check that indeed X, Y, and Z are bound as described if you wish.
Not that it matters that much. Prolog is really for indeterministically searching a large/infinite space such as in planning (and yes it can be used for theorem proving and program verification as well). It's always fun to see newcomers trying to implement pointless low-level stuff such as list primitives when coming from a functional programming background or even getters/setters.
[1]: https://quantumprolog.sgml.net/browser-demo/browser-demo.htm…
Karrot_Kream
This looks like it's using Prolog to be a rewriting engine into C? It reminds me a bit of M4 but seems like something better suited to something like Maude [1]?
[1]: https://maude.cs.illinois.edu/wiki/Maude_Overview
c-smile
Interesting timing…
I've just made something similar but with JS: C modules in JS.
C module gets compiled to native code on load. I think that clear separation of execution models is a good thing.
https://sciter.com/c-modules-in-sciter/
https://sciter.com/here-we-go/
xonix
Long time ago I was obsessed with Prolog. I found that if you bend it enough you could implement imperative code (a-la JavaScript) evaluation, still being valid Prolog:
https://github.com/xonixx/prolog-experiments/blob/main/Prolo…
This was super funny, but obviously absolutely useless
coliveira
The main advantage of Prolog is that it forces you to think in different ways to solve problems. The main weakness of Prolog is that it forces you to think in different ways to solve problems. Especially because most existing libraries are written in procedural style, so they don't work well with prolog. In the end you'll have some difficulty to integrate with existing code.
sinuhe69
Is that not what Picat is all about? Imperative + Declarative Constraint Programming
[0] http://picat-lang.org/