Here’s the code with the thread PIDs doubling as message queue PIDs to save a few lines and arguments:
ping(0, Pong_PID) :-
thread_send_message(Pong_PID, 'finished'),
format("Ping finished~n", []), !.
ping(N, Pong_PID) :-
thread_self(Ping_PID),
thread_send_message(Pong_PID, msg('Ping', Ping_PID)),
thread_get_message(Ping_PID, Msg),
format("Ping received ~w~n", [Msg]),
succ(M, N),
ping(M, Pong_PID).
pong :-
repeat,
thread_get_message(Msg),
( Msg \== 'finished'
-> msg(Ping, Ping_PID) = Msg,
format("Pong received ~w~n", [Ping]),
thread_send_message(Ping_PID, 'Pong'),
fail
; format("Pong finished~n", []),
!
).
start :-
thread_create(pong, Pong_PID),
thread_create(ping(3, Pong_PID), Ping_PID),
thread_join(Pong_PID),
thread_join(Ping_PID).
A reason I prefer the original, more verbose version comes from watching some Youtube lectures given by Joe Armstrong, Alan Kay, Carl Hewitt etc who stressed that message queues were originally a core part of object oriented programming as well as parallel programming – a key idea which somehow got lost in modern, popular programming.
I also prefer placing anything resembling a stream within setup_call_cleanup/3 which this more terse version doesn’t lend itself to.