FLENG as a "Plan B"
I like KL1, but KLIC is taking a long time to be put on a software forge; and a lot of the things I think would be a good idea (e.g. kqueue, pthreads) are already in FLENG. So I've been taking a look at the latter again.
FLENG implements a few languages. Flat Guarded Horn Clauses (FGHC) is the one I'm interested in, it's almost identical to KL1. Library is different though. Similar to the difference between Java and C#. I try to explain a few fragments I wrote (for playing around only) in the following.
There are various examples, but the main value I'm getting out of this is exposure to a new programming paradigm. I remember being impressed by some highly parallel algorithms in more conventional languages in the past and wondering how the author came up with them. In a concurrent LP language you have no choice, this is the natural way to write code. But it takes a little reading to figure out, I can recommend the "Strand book" that the FLENG site also links to.
Parallelism
Tasks can be distributed over pthreads by just using '@'. The semantics of logic variables ensure thread-safety, and although it's not used here an incomplete list data structure can serve as a queue (this is all similar to other Concurrent Logic Programming languages, and well explained in the Strand book).
-initialization(main). main :- get_1(Value1)@2, % distributed to node 2 get_2(Value2)@3, % and 3 Sum is Value1 + Value2, writeln(Sum). get_1(Res) :- Res = 1. get_2(Res) :- Res = 2.
- "initialization" directive is used for the main module of an app. "module" is the alternative for child modules with >1 entry point.
- You can use threads(NumNodes) to get the number of nodes, and self(CurrNode) to see what the current predicate is executing on. This interacts with a "+THREADS" command-line option to set how many OS threads to use (this should be the number of cores on the system).
- Note in get_1 & get_2 that you can't unify variables in the clause head, you have to get past the (non-existent in this case) guard first and "commit". Hence the term "committed choice language".
Metaprogramming
-initialization(main).
-mode(to_zero(?, ^)).
to_zero(X, Y) :- X > 0 | Y is X - 1.
to_zero(X, Y) :- X < 0 | Y is X + 1.
main :-
get_module('', Mod), % '' is the default top-level module
module_name(Mod, Nm),
all_modules(Mods),
Pred = {'to_zero', -40, Res}, % A tuple like this is identical to the Prolog functor to_zero(-40, Res)
call(Pred), % apply() is also available, with semantics identical to Prolog
print_res(Nm, Mods, Res).
-mode(print_res(?, ?, ?)).
print_res(Nm, Mods, Res) :-
writeln(Nm) & % '&' forces sequencing, usually bad style when you can run all child clauses in parallel
writeln(Mods) &
writeln(Res)
Although not as dynamic as full Prolog, facilities like the above are relatively cheap, comparable to dlsym() in C but I prefer this code.
I haven't used it until now, but you can specify modes (input ? or output ^) for all predicates. I think this turns the language into "moded FGHC" according to Prof. Ueda.
I/O
Some thought was put into this. All networking I/O is via kqueue/epoll under the hood, and there are some fast-paths. For example, here's a minimal UNIX cat(1):
-initialization(run). run :- io:transfer(0, 1, _).
GUI
-initialization(main).
click(Ev) :- send(Ev, click).
main :-
ezd:init(EZD0),
open_port(Events, S),
do_ui(Events, S, EZD0, _).
do_ui(Events, S)-EZD :-
bb:std_events(Events)-EZD,
label("0")-EZD,
bb:button("b", {300, 10}, {200, 30}, "Count", '':click(Events))-EZD,
loop(S, EZD, 0).
label(Val)-EZD :- bb:label("t", {10, 10}, Val)-EZD.
loop([_|S2], EZD0, Count) :-
C is Count + 1,
number_to_list(C, CL, []),
label(CL, EZD0, EZD),
loop(S2, EZD, C).
There's a GUI implemented in PCN (an alternative to FGHC) on top of SDL2. The above is a translation of one of the PCN examples into FGHC, which was a fairly mechanical process. Looks won't win any aesthetic prizes, but it's serviceable and for commercial work a Web-style UI would probably be required anyway.
- Pixel coordinate system is used.
- Events from the UI is one of those incomplete lists/ports that I mentioned above.
FFI
I decided to try out something that I had to change the KLIC runtime library for, calling the UNIX syslog function. I was able to do it in FLENG in ordinary code, without needing to dive into the compiler or runtime.
-foreign('#include ').
-foreign([openlog(string, integer, integer),
syslog(integer, string),
closelog(-void)]).
-initialization(run).
run :-
log_pid(LogPid),
log_local0(LogLocal0),
log_info(LogInfo),
openlog('foo', LogPid, LogLocal0, Ok0),
when(Ok0, syslog(LogInfo, 'bar', Ok1)),
when(Ok1, closelog(_)).
-foreign(const(log_pid = 'LOG_PID':integer)).
-foreign(const(log_local0 = 'LOG_LOCAL0':integer)).
-foreign(const(log_info = 'LOG_INFO':integer)).
Some random observations:
- Because it's run through the C compiler (i.e. not direct dlsym/libffi calls), we can easily import preprocessor constants.
- Need to use when() to enforce ordering, remember the language is and-parallel.
- Found some bugs, not all of which are fixed yet, but the project is active and maintained.
In summary, FLENG is maturing rapidly.