semantics of DCG phrase as monoid action.

213 views
Skip to first unread message

Kuniaki Mukai

unread,
Sep 1, 2018, 9:38:49 AM9/1/18
to SWI-Prolog
Are there articles on semantics of DCG based on monoid action ?
I thought for a long time that DCG is a monoid action as follows,
here in a simplified functional version.


Let G be a set, and (,) : G x G -> G a binary function.
A triple (G, (,), true) is a monoid if
the following hold for each x in G.

(Unit left) (true, x) = x
(Unit right) (x, true) = x
(Assoicative) (x, (y, z)) = ((x, y), z)

Let S be a set, and d a function G x S -> S, then
a triple (G, S, d) is a DCG if the following
hold for each x,y in G and s in S.

d(true, s) = s
d((x, y), s) = d(y, d(x, s))

However I heard that "officially", DCG is only for lists,
at which I was surprised and disappointed because
the monoid action is so widely accepted useful notion in math.

Also, before 2015 I wrote a codes implementing
a "yet-another-markup-language" with DCG phrases as monoid action
on orderd pairs of a list and a "dict" (as state),which might be related to what
is discussed currently in other thread. The codes is still in pack/pac, and useful for me.

I don't want to think that I am doing "illegal" against official
DCG. I am grateful for any help that supports I am not guilty.

Kuniaki Mukai

Barb Knox

unread,
Sep 1, 2018, 5:58:02 PM9/1/18
to SWI-Prolog, Kuniaki Mukai
> On 2/09/2018, at 01:38, Kuniaki Mukai <kuniak...@gmail.com> wrote:
>
> Are there articles on semantics of DCG based on monoid action ?
> I thought for a long time that DCG is a monoid action as follows,
> here in a simplified functional version.
>
>
> Let G be a set, and (,) : G x G -> G a binary function.
> A triple (G, (,), true) is a monoid if
> the following hold for each x in G.
>
> (Unit left) (true, x) = x
> (Unit right) (x, true) = x
> (Assoicative) (x, (y, z)) = ((x, y), z)
>
> Let S be a set, and d a function G x S -> S, then
> a triple (G, S, d) is a DCG if the following
> hold for each x,y in G and s in S.
>
> d(true, s) = s
> d((x, y), s) = d(y, d(x, s))
>
> However I heard that "officially", DCG is only for lists,

I don't know who these officials are, but I feel free to use DCGs for whatever I want without fear of any official sanction. Be wild and crazy.

> at which I was surprised and disappointed because
> the monoid action is so widely accepted useful notion in math.

I do not see why any particular mathematical notion should be expected to be applicable to DCGs, or to anything else for that matter.


> Also, before 2015 I wrote a codes implementing
> a "yet-another-markup-language" with DCG phrases as monoid action
> on orderd pairs of a list and a "dict" (as state),which might be related to what
> is discussed currently in other thread. The codes is still in pack/pac, and useful for me.
>
> I don't want to think that I am doing "illegal" against official
> DCG. I am grateful for any help that supports I am not guilty.

Really, legality and guilt in no way enter into this.

And if for whatever reason you suffer a guilty conscience for having "abused" DCGs, do an appropriate penance and get past it. Nobody cares.


> Kuniaki Mukai

--
---------------------------
| BBB b \ Barbara at LivingHistory stop co stop uk
| B B aa rrr b |
| BBB a a r bbb | ,008015L080180,022036,029037
| B B a a r b b | ,047045,L014114L4.
| BBB aa a r bbb |
-----------------------------

Samer Abdallah

unread,
Sep 1, 2018, 6:06:19 PM9/1/18
to Kuniaki Mukai, SWI-Prolog
I think this is a reasonable view. Indeed, perhaps the
simplest model is to have G as the set of functions of
type (s -> s) in which case the monoid operator is function
composition and the ‘zero’ is the identity function.

I know some might disagree, but I think using DCGs with an
arbitrary state type is just fine — the way DCG goals can interoperate
with any state transformer predicate is too convenient, seems
theoretically sound (because it’s just a shorthand for function or predicate
composition) and has never caused me any problem or unexpected behaviour
(well, except when SWI Prolog’s phrase/3 was intentionally modified
to work on lists only, and I had to change every instance of phrase/3
with call_dcg/3).

One reason that DCGs might not exactly fit the theory you describe is
non-determinism, so a DCG rule corresponds to a relation, not a function.
Still, if G is a set of binary relations on S (ie G =< Pow(S x S)), (*) is
relation composition
F * G = { (x,z) | Exists y. (x,y) in F and (y,z) in G }
and id = { (x,x) | x in S } then (G, *, id) is still a monoid.

Samer
> --
> You received this message because you are subscribed to the Google Groups "SWI-Prolog" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to swi-prolog+...@googlegroups.com.
> Visit this group at https://groups.google.com/group/swi-prolog.
> For more options, visit https://groups.google.com/d/optout.

signature.asc

Peter Ludemann

unread,
Sep 1, 2018, 6:22:50 PM9/1/18
to SWI-Prolog
Possibly related:

The paper has an example with the State monad and delimited continuations for implementing DCGs. I haven't fully comprehended the details of the paper.
There's also some discussion on the details of delimited continuations here:


Peter Ludemann

unread,
Sep 1, 2018, 6:59:59 PM9/1/18
to SWI-Prolog
As for DCGs being only for lists, the XSB manual has a section on an alternative representation.

search for "Definite Clause Grammars and Tabling".

EDCGs extend the notion of "accumulators" to allow any predicate with an input and output.
For DCGs, this accumulator has traditionally been named 'C', and is often compiled out -- its definition for EDCGs is:
    edcg:acc_info(fwd, T, In, Out, Out=[T|In]).
and the reverse-order accumulator is:
    edcg:acc_info(rev, T, Out, In, Out=[T|In]).

Kuniaki Mukai

unread,
Sep 1, 2018, 11:19:17 PM9/1/18
to Samer Abdallah, SWI-Prolog

Hi Samer,

I am glad to see your response. I feel we share something in understanding
DCG including this part of your reply.

> One reason that DCGs might not exactly fit the theory you describe is
> non-determinism, so a DCG rule corresponds to a relation, not a function.
> Still, if G is a set of binary relations on S (ie G =< Pow(S x S)), (*) is
> relation composition
> F * G = { (x,z) | Exists y. (x,y) in F and (y,z) in G }
> and id = { (x,x) | x in S } then (G, *, id) is still a monoid.

Although I am not familiar with Haskell programming at all,
I am interested in relation between Haskell monad as Kleisli construction
and DCG as monoid action. They have “monoid” in common.
The influential person mentioned in replying to Barb gave a mysterious but interesting comment
in another post: roughly “While Haskell has monad, Prolog has DCG.”

Kuniaki Mukai

















Kuniaki Mukai

unread,
Sep 1, 2018, 11:22:01 PM9/1/18
to Peter Ludemann, SWI-Prolog

Hi Ludemann,

Thank you for pointers. I will check them soon.  Delimited continuation is a keyword 
often seen in this forum, but I never paid attention as irrelevant topics to me.
My interest is narrow.

Kuniaki Mukai

Kuniaki Mukai

unread,
Sep 1, 2018, 11:48:34 PM9/1/18
to Barb Knox, SWI-Prolog

On Sep 2, 2018, at 6:57, Barb Knox <Bar...@LivingHistory.co.uk> wrote:

On 2/09/2018, at 01:38, Kuniaki Mukai <kuniak...@gmail.com> wrote:

Are there articles on semantics of DCG based on monoid action ?
I thought for a long time that DCG is a monoid action as follows,
here in a simplified functional version.


Let G be a set, and (,) : G x G -> G a binary function.
A triple (G, (,), true) is a monoid if
the following hold for each x in G.

(Unit left) (true, x) = x
(Unit right) (x, true) = x
(Assoicative) (x, (y, z)) = ((x, y), z)

Let S be a set, and d a function G x S -> S, then
a triple (G, S, d) is a DCG if the following
hold for each x,y in G and s in S.

d(true, s) = s
d((x, y), s) = d(y, d(x, s))

However I heard that "officially", DCG is only for lists,

I don't know who these officials are, but I feel free to use DCGs for whatever I want without fear of any official sanction.  


I saw it in this forum years ago, which was posted by an influential person whom many prolgers including me respect.
It was not “official” but something close. 

Be wild and crazy.

Impressive advice !    I will keep this in mind, though I am afraid I am too mild in nature for that.

Kuniaki Mukai




Kuniaki Mukai

unread,
Sep 2, 2018, 9:54:45 AM9/2/18
to Peter Ludemann, SWI-Prolog
Consulting the manual for reset/3,


the reset/shift works as I expected thanks to the clear description of the predicate , 
though the relation to Haskell monad was perfectly unclear  because of lack of basic knowledge 
in Haskell  monad.  Also I was new to the delimited continuation, and now I got interested.

?- reset((writeln(hello), shift(Ball), writeln(Ball)),  Ball, Cont),
member(Ball, [world1, world2, world3]), 
call(Cont).

%@ hello
%@ world1
%@ Ball = world1,
%@ Cont = call_continuation(['$cont$'(<clause>(0x7fa5fb529d00), 15, '<inactive>', zdd, 120, '<inactive>', writeln(world1))]) ;
%@ world2
%@ Ball = world2,
%@ Cont = call_continuation(['$cont$'(<clause>(0x7fa5fb529d00), 15, '<inactive>', zdd, 120, '<inactive>', writeln(world2))]) ;
%@ world3
%@ Ball = world3,
%@ Cont = call_continuation(['$cont$'(<clause>(0x7fa5fb529d00), 15, '<inactive>', zdd, 120, '<inactive>', writeln(world3))]).

- K.M.

Samer Abdallah

unread,
Sep 2, 2018, 10:44:24 AM9/2/18
to Kuniaki Mukai, Peter Ludemann, SWI-Prolog
Hi Kuniaki,
There are some nice papers on the relationship between monads and
delimited continuations by Filinski:

[1] Andrzej Filinski. Representing monads. In Proceedings of the 21st ACM SIGPLAN- SIGACT symposium on Principles of programming languages, pages 446–457. ACM, 1994.
[2] Andrzej Filinski. Representing layered monads. In Proceedings of the 26th ACM SIGPLAN-SIGACT symposium on Principles of programming languages, pages 175–188. ACM, 1999.


Essentially, any monad can also be implemented using delimited continuations.
The ‘plumbing’ provided by the monad (bind, return) is replaced with ordinary code
in the host language and when the effect provided by the monad is needed, 
shift or similar is used to capture the continuation. The effect handler then treats
the continuation (a function from some local value to the end result of the computation)
almost exactly like the Kleisli arrow for the monad, ie a function of type a -> M b.
The effect handler can literally use the monad itself to represent the computation
following the effect request.

Samer
signature.asc

Dan

unread,
Sep 2, 2018, 12:46:14 PM9/2/18
to SWI-Prolog
oh boy, 

I read this thread, but, not much sunk in ... 

monads, 
delimited continuation
co-routines
kleisli ...

can anyone translate that for a mere mortal (prolog) developers, into simple requirements statements (what does the programmer needed in his/her toolkit and why) and design (how its addressed):-)

thanks,

Dan

Kuniaki Mukai

unread,
Sep 2, 2018, 1:03:27 PM9/2/18
to Samer Abdallah, Peter Ludemann, SWI-Prolog

On Sep 2, 2018, at 23:44, Samer Abdallah <seventy...@gmail.com> wrote:

Hi Kuniaki,
There are some nice papers on the relationship between monads and
delimited continuations by Filinski:

[1] Andrzej Filinski. Representing monads. In Proceedings of the 21st ACM SIGPLAN- SIGACT symposium on Principles of programming languages, pages 446–457. ACM, 1994.
[2] Andrzej Filinski. Representing layered monads. In Proceedings of the 26th ACM SIGPLAN-SIGACT symposium on Principles of programming languages, pages 175–188. ACM, 1999.

Thanks, I will check them,  hoping they are downloadable.
I am looking forward to seeing into Haskell monad implementation on this occasion
via delimited continuation. I feel happy to find experts on this in the forum.

-K.M.

Kuniaki Mukai

unread,
Sep 3, 2018, 11:48:10 PM9/3/18
to SWI-Prolog

Here is a first try on using reset/shift.

p(N):- writeln(N), N0 is N+1, shift(N0).

print_integers_upto(N, U):- reset(p(N), N0, _),
(N0 =< U-> print_integers_upto(N0, U); true).

?- print_integers_upto(1, 5).
%@ 1
%@ 2
%@ 3
%@ 4
%@ 5
%@ true.

Is reset/shift efficient ? Here is a my “bench mark” test.

q(N):- N0 is N+1, shift(N0).
integers_upto(N, U):- reset(q(N), N0, _),
(N0 =< U-> integers_upto(N0, U); true).

?- time(integers_upto(1, 100 000 000)).
%@ % 500,000,002 inferences, 26.820 CPU in 27.006 seconds (99% CPU, 18642556 Lips)
%@ true.

?- time(forall(between(1, 100 000 000, N), true)).
%@ % 100,000,002 inferences, 5.121 CPU in 5.132 seconds (100% CPU, 19529221 Lips)
%@ true.

Is this good ? Anyway it is enough for me for now.

Now I got interested in reset/shift for many reasons, lazy computation, for instance.

The short description of reset/shift in the SWI-Prolog manual
was exceptionally clear for me. I could not understand them
seen in functional programming meterials, which I am ashamed a little though.

Kuniaki Mukai

Dan

unread,
Sep 4, 2018, 5:54:53 AM9/4/18
to SWI-Prolog
Hi Kuniaki, 

Thank you for the code example, 

Could you perhaps annotate the code with some explanation -- what each operation does .

I understand that rest/shift has some analogy to catch/throw, with different semantics related to backtracking. Perhaps with a bit further explaining I could understand this simple example. 

thank you,

Dan

Jan Wielemaker

unread,
Sep 4, 2018, 6:45:30 AM9/4/18
to Kuniaki Mukai, SWI-Prolog
On 04/09/18 05:48, Kuniaki Mukai wrote:
> ?- time(integers_upto(1, 100 000 000)).
> %@ % 500,000,002 inferences, 26.820 CPU in 27.006 seconds (99% CPU, 18642556 Lips)
> %@ true.
>
> ?- time(forall(between(1, 100 000 000, N), true)).
> %@ % 100,000,002 inferences, 5.121 CPU in 5.132 seconds (100% CPU, 19529221 Lips)
> %@ true.
>
> Is this good ? Anyway it is enough for me for now.

Better than I'd anticipated :)

> Now I got interested in reset/shift for many reasons, lazy computation, for instance.
>
> The short description of reset/shift in the SWI-Prolog manual
> was exceptionally clear for me. I could not understand them
> seen in functional programming meterials, which I am ashamed a little
> though.

Thanks. I had a hard time grabbing the concept from the original text by
Tom, Benoit and Bart, most likely because Tom is pretty fluent with the
functional paradigm. Note that delimited continuations are not the only
way to create coroutines in SWI-Prolog. Engines can do the same and you
can do a lot of funny stuff with them. See for example the lazy list
library
(http://www.swi-prolog.org/pldoc/doc/_SWI_/library/lazy_lists.pl).
Engines are based on the work by Paul Tarau and came to SWI-Prolog for
Kyndi.

Both techniques imply some copying. For delimited continuations it
implies copying (some frames of) the environment stack. For engines it
is the task sent to the engine and the result received from it that is
copied. A serious problem with delimited continuations is the
interaction with choice point pruning (!, ->).

Both invite creativity :)

Enjoy --- Jan

Kuniaki Mukai

unread,
Sep 4, 2018, 7:26:08 AM9/4/18
to Dan, SWI-Prolog


> On Sep 4, 2018, at 18:54, Dan <gros...@gmail.com> wrote:
>
> Hi Kuniaki,
>
> Thank you for the code example,
>
> Could you perhaps annotate the code with some explanation -- what each operation does .
>
> I understand that rest/shift has some analogy to catch/throw, with different semantics related to backtracking.
> Perhaps with a bit further explaining I could understand this simple example.

Me too on reset/shift and analogy with catch/throw. My codes is short and uses only primitives so that
I could not find any place to add annotations, If added, surely it got worse.

As a second exercise, I will write for “infinite list” of the prime numbers, which
is a well known standard exercise for lazy computation. In fact I wrote an freeze/2 based one
a long long time ago with ease. So I hope I can do it also in reset/shift. If some post
the codes here, it is welcome. My purpose is to get familiar with use of reset/shift.

-K.M.












Samer Abdallah

unread,
Sep 4, 2018, 7:36:56 AM9/4/18
to Kuniaki Mukai, Dan, SWI-Prolog
Kuniaki, Dan,

I have some slides including an introduction to delimited continuations
in functional and logic programming here:
http://stoics.org.uk/plp/plp2017/presentations/invited_2.pdf

Also, I would love to be able to write a short, concise, and comprehensible
introduction to monads, but I’m not enough of an expert on it to do that.
I can only waffle on about it for a while and hope that something starts
to make sense.

Samer

Kuniaki Mukai

unread,
Sep 4, 2018, 8:10:14 AM9/4/18
to Samer Abdallah, Dan, SWI-Prolog

On Sep 4, 2018, at 20:36, Samer Abdallah <seventy...@gmail.com> wrote:

Kuniaki, Dan,

I have some slides including an introduction to delimited continuations
in functional and logic programming here:
http://stoics.org.uk/plp/plp2017/presentations/invited_2.pdf

I have checked it. It seems excellent, but unfortunately
I could interpret it only a little for now. I would like to take more time
to read again.


Also, I would love to be able to write a short, concise, and comprehensible
introduction to monads, but I’m not enough of an expert on it to do that.

Exciting ! I am not only one who expects it, but many of this forum,  I believe.  

I can only waffle on about it for a while and hope that something starts
to make sense.

I am looking forward to reading it in any form.

-K.M.

Kuniaki Mukai

unread,
Sep 4, 2018, 11:21:04 AM9/4/18
to SWI-Prolog
Hi,

Here is a second exercise of mine on using reset/shift, which might be interested 
also  by others 

Suppose the following four simple predicates,
each of which calculates  expressions that
has only one specific symbols. They do not
calculate expressions which has two different operators,
e.g. 1 + 2*3.

add(X+Y, Z) :- !, add(X, U), add(Y, V), Z is U + V.
add(X, X).

prefix_minus(-X, Y) :- !, prefix_minus(X,  U), Y is -U.
prefix_minus(X, X).

subtract(X-Y, Z) :- !, subtract(X, U), subtract(Y, V), Z is U-V.
subtract(X, X).

multiply(X*Y, Z) :- !, multiply(X, U), multiply(Y, V), Z is U*V.
multiply(X, X).

How to combine these four using reset/shift so that it can calculate
extended expressions which has different operators.

My tentative and partial answer is the calc/2 below.
Although I am not satisfied with the answer because it modifies the given unit
clauses, for exampe, I have to learn much more about reset/shift before improving.

Suggetions are welcome.

-----------
Queries.
----------

?- calc(1, V).
%@ V = 1.

?- calc(1+2*3, V).
%@ V = 7.

?- calc(1+ -(2-1)+4, V).
%@ V = 4.

---------
calc/2
---------

calc(X, Y):- ball_to_goal(X, Y, G), continue(G).

%
add(X+Y, Z) :- !, add(X, U), add(Y, V), Z is U + V.
add(X, Y):- shift(X->Y).

prefix_minus(-X, Y) :- !, prefix_minus(X,  U), Y is -U.
prefix_minus(X, Y)  :- shift(X->Y).

subtract(X-Y, Z) :- !, subtract(X, U), subtract(Y, V), Z is U-V.
subtract(X, Y) :- shift(X->Y).

multiply(X*Y, Z) :- !, multiply(X, U), multiply(Y, V), Z is U*V.
multiply(X, Y) :- shift(X->Y).

%
continue(true):-!.
continue(0):-!.
continue((A, B)):- !, continue(A), continue(B).
continue(Cont):- reset(Cont, A->B, Cont0),
ball_to_goal(A, B, G),
continue(G),
continue(Cont0).

%
ball_to_goal(X, _, true):- var(X), !.
ball_to_goal(X, X, true):- integer(X), !.
ball_to_goal(+(A,B), Y, add(+(A,B), Y)):- !.
ball_to_goal(-(A,B), Y, subtract(-(A,B), Y)):- !.
ball_to_goal(-(A), Y, prefix_minus(-(A), Y)):- !.
ball_to_goal(*(A, B), Y, multiply(*(A, B), Y)):- !.

-K.M.

Kuniaki Mukai

unread,
Sep 5, 2018, 4:36:16 AM9/5/18
to SWI-Prolog
As a final exercise on using reset/shift, I have managed to write prime number generator using reset/shift.
I am new to reset/shift, and feeling something interesting in shift/rest programming.
Comments on this codes below are appreciated.

?- print_primes_endless.
< many primes printed >

?- time((length(Ps, 8), primes(Ps))).
%@ % 296 inferences, 0.000 CPU in 0.000 seconds (89% CPU, 7400000 Lips)
%@ Ps = [2, 3, 5, 7, 11, 13, 17, 19].

?- time((length(Ps, 10000), primes(Ps))).
%@ % 1,013,210,239 inferences, 87.725 CPU in 87.834 seconds (100% CPU, 11549892 Lips)
%@ Ps = [2, 3, 5, 7, 11, 13, 17, 19, 23|...].

Shift/reset is used in the following tiny predicates
as producer/consumer.

%
nat(N):- J is N+1, shift(J), nat(J).
%
init_nat(J, Cont):- reset(nat(0), J, Cont).
%
next_nat_cont(Cont, J, Cont0) :- reset(Cont, J, Cont0).

% main
print_primes_endless:- init_nat(_, Cont),
print_primes([], Cont).
%
print_primes(Ps, Cont):- next_prime(Ps, Cont, P, Cont1),
writeln(P),
print_primes([P|Ps], Cont1).
%
primes(Ps):- init_nat(_, Cont),
primes(Ps, [], Cont, _).
%
primes([], _, _, _).
primes([Q|Qs], Ps, Cont, Cont0):-
next_prime(Ps, Cont, Q, Cont1),
primes(Qs, [Q|Ps], Cont1, Cont0).
%
next_prime(Ps, Cont, P, Cont1):-
next_nat_cont(Cont, J, Cont0),
( member(Q, Ps), mod(J, Q) =:= 0
-> next_prime(Ps, Cont0, P, Cont1)
; P = J, Cont1 = Cont0
).


Kuniaki Mukai

unread,
Sep 5, 2018, 7:12:46 AM9/5/18
to Jan Wielemaker, SWI-Prolog


> On Sep 4, 2018, at 19:45, Jan Wielemaker <j...@swi-prolog.org> wrote:
>
> On 04/09/18 05:48, Kuniaki Mukai wrote:
>> ?- time(integers_upto(1, 100 000 000)).
>> %@ % 500,000,002 inferences, 26.820 CPU in 27.006 seconds (99% CPU, 18642556 Lips)
>> %@ true.
>>
>> ?- time(forall(between(1, 100 000 000, N), true)).
>> %@ % 100,000,002 inferences, 5.121 CPU in 5.132 seconds (100% CPU, 19529221 Lips)
>> %@ true.
>>
>> Is this good ? Anyway it is enough for me for now.
>
> Better than I'd anticipated :)

Really ? I'm glad to hear that.

>
>> Now I got interested in reset/shift for many reasons, lazy computation, for instance.
>>
>> The short description of reset/shift in the SWI-Prolog manual
>> was exceptionally clear for me. I could not understand them
>> seen in functional programming meterials, which I am ashamed a little
>> though.
>
> Thanks. I had a hard time grabbing the concept from the original text by
> Tom, Benoit and Bart, most likely because Tom is pretty fluent with the
> functional paradigm. Note that delimited continuations are not the only
> way to create coroutines in SWI-Prolog. Engines can do the same and you
> can do a lot of funny stuff with them. See for example the lazy list
> library
> (http://www.swi-prolog.org/pldoc/doc/_SWI_/library/lazy_lists.pl).
> Engines are based on the work by Paul Tarau and came to SWI-Prolog for
> Kyndi.

As for me it is not only shift/reset but also throw/catch that I understood first
reading descriptions in SWI-Prolog. I appreciate your hidden
efforts on digesting difficult concepts for us. Thanks deeply again.

>
> Both techniques imply some copying. For delimited continuations it
> implies copying (some frames of) the environment stack. For engines it
> is the task sent to the engine and the result received from it that is
> copied. A serious problem with delimited continuations is the
> interaction with choice point pruning (!, ->).

Agree. I am noticing possible problems of cut(!) behavior because of change
of the context of ! in the continuation to that of the reset (catcher).
For now, I am enjoying shift/reset programming in which the cut is safely
ignored, that is, the continuation is cut-free.

-K.M.

Kuniaki Mukai

unread,
Sep 7, 2018, 8:21:53 AM9/7/18
to SWI-Prolog, Jan Wielemaker


Hi

Here is another use of reset/shift, which
is intended to be a simplest example of “DCG"
with multi tracks. Now I am suspecting that
Samer already has done much advanced things on that.

I hope this simplest example might
encourage the reader to use who spent so far little
attention to rest/shift like me but is
interested in something called continuation
in the literature. I think reset/shift is a gift from Jan W. to us.

% ?- continue(test([a, a, b, b, c, c], R), []).

%@ catch_ball(update(a,1),[],[a-2])
%@ catch_ball(update(a,2),[a-2],[a-3])
%@ catch_ball(update(b,1),[a-3],[b-2,a-3])
%@ catch_ball(update(b,2),[b-2,a-3],[b-3,a-3])
%@ catch_ball(update(c,1),[b-3,a-3],[c-2,b-3,a-3])
%@ catch_ball(update(c,2),[c-2,b-3,a-3],[c-3,b-3,a-3])
%@ catch_ball(update(c,3),[c-3,b-3,a-3],[c-4,b-3,a-3])
%@ R = [1, 2, 1, 2, 1, 2] .

test([], []):-!.
test([A|As], [X|Bs]):- shift(update(A, X)),
test(As, Bs).

%
continue(true, _).
continue(0, _).
continue(Cont, S):- reset(Cont, Ball, Cont0),
catch_ball(Ball, S, S0),
writeln(catch_ball(Ball, S, S0)),
continue(Cont0, S0).
%
catch_ball(update(A, X), S, [A-X0|S0]):- select(A-X, S, S0),!,
X0 is X+1.
catch_ball(update(A, 1), S, [A-2|S]).

Kuniaki Mukai

unread,
Sep 7, 2018, 3:03:36 PM9/7/18
to SWI-Prolog
Hi,

The reset/shift is powerful. I have experimentally added shift to DCG
phrase, by adding the exactly one clause to expand_phrase/5 in
pack/pac/expand-pac.pl:

expand_phrase(shift(X), M, {shift(Y)}, P, Q) :-
expand_arg(X, M, Y, P, Q).

Very simple. And then I ran this "extended DCG" with integers
as states for testing. It was a little bit surprise to see
it works as expected. The shift also works for pred (lmabda).
For now I see no problem to extend the state to dict-like one, for instance.

continue(true, _).
continue(0, _).
continue(Cont, S):- reset(Cont, Ball, Cont0),
( var(Ball) -> true
; call(Ball, S, S0),
continue(Cont0, S0)
).

numbering(As, As) --> [].
numbering([X-N|As], Bs) --> [X], shift(counter(N)),
numbering(As, Bs).

counter(S, S, S0):- succ(S, S0).

% Using pred (lambda) for shift.
pred_numbering(As, As) --> [].
pred_numbering([X-N|As], Bs) --> [X],
shift(pred(N, [N, J]:- succ(N, J))),
pred_numbering(As, Bs).

% ?- continue(numbering(Zip, [], [a, b, c], R), 1).
%@ Zip = [],
%@ R = [a, b, c] ;
%@ Zip = [a-1],
%@ R = [b, c] ;
%@ Zip = [a-1, b-2],
%@ R = [c] ;
%@ Zip = [a-1, b-2, c-3],
%@ R = [] ;
%@ false.

% ?- continue(pred_numbering(Zip, [], [a, b, c], R), 1).
%@ Zip = [],
%@ R = [a, b, c] ;
%@ Zip = [a-1],
%@ R = [b, c] ;
%@ Zip = [a-1, b-2],
%@ R = [c] ;
%@ Zip = [a-1, b-2, c-3],
%@ R = [] .

I am not sure this is a way to extended DCG.
I am a new to shift/reset, and currently I am enjoying the new toy
for me.

-K.M.

Samer Abdallah

unread,
Sep 10, 2018, 4:15:06 PM9/10/18
to Kuniaki Mukai, SWI-Prolog
Hi Kuniaki,

Sorry I wasn’t able to comment on your examples sooner.
Regarding this particular example, while technically correct,
does not use delimited continuations in an essential way.

The clue is in the code for continue/1:

On 4 Sep 2018, at 16:20, Kuniaki Mukai <kuniak...@gmail.com> wrote:

continue(true):-!.
continue(0):-!.
continue((A, B)):- !, continue(A), continue(B).
continue(Cont):- reset(Cont, A->B, Cont0),
ball_to_goal(A, B, G),
continue(G),
continue(Cont0).

In the 4th clause, one of two things can happen:

(a) Cont completes without calling shift/1, in which case the 
recursion terminates. More on this below.

(b) Cont calls shift(X->Y), in which case, X->Y is unified with A->B
and Cont0 with the delimited continuation. After this, the clause
calls some goals which depend _only_ on the original X->Y. Then it
resumes the original goal by calling the continuation. 

This lack of dependence on any other information that might be 
passed ‘around’ the call to reset/3, followed by the unconditional
resumption of the continuation has the same semantics as an ordinary 
predicate call: instead of calling shift(X->Y), you can simply call

eval(X->Y) :- ball_to_goal(X, Y, G), continue(G).

which is basically the same as calc/2. Without calls to shift/1, 
continue/1 reduces to 
continue(true) :- !.
continue((A,B)) :- !, continue(A), continue(B).
continue(G) :- call(G).
which is just a very skinny meta-interpreter. Might as well just use
call/1 directly instead of continue/1. The end result is something like this:

__________
calc(A, B) :- expr_to_goal(A, B, G), call(G).

add(X+Y, Z) :- !, add(X, U), add(Y, V), Z is U + V.
add(X, Y):- calc(X,Y).

prefix_minus(-X, Y) :- !, prefix_minus(X,  U), Y is -U.
prefix_minus(X, Y)  :- calc(X, Y).

subtract(X-Y, Z) :- !, subtract(X, U), subtract(Y, V), Z is U-V.
subtract(X, Y)    :- calc(X, Y).

multiply(X*Y, Z) :- !, multiply(X, U), multiply(Y, V), Z is U*V.
multiply(X, Y)    :- calc(X, Y).

expr_to_goal(X, _, true):- var(X), !.
expr_to_goal(X, X, true):- integer(X), !.
expr_to_goal(+(A,B), Y, add(+(A,B), Y)):- !.
expr_to_goal(-(A,B), Y, subtract(-(A,B), Y)):- !.
expr_to_goal(-(A), Y, prefix_minus(-(A), Y)):- !.
expr_to_goal(*(A, B), Y, multiply(*(A, B), Y)):- !.
__________

Going back to case (a), when continue/1 is called with a goal
that does not call shift/1, Cont0 is unified with 0 and the ‘Ball’
parameter is not touched.
Since it is already partially instantiated to A->B, this leaves A and
B as variables. Typically, this condition (Cont0 = 0) should be 
detected immediately so that no unnecessary or meaningless
further processing is done. In this example, there is no check,
so the clause continues as if A and B were meaningful values.
It so happens that ball_to_goal/3, when called with a variable for 
the first argument, simply unifies G with true. Since continue(true) 
and continue(0) are both facts, the recursion terminates. I won’t say 
this is luck, as I expect you found you had to define continue/1 in
this way to make it work, but I think this reinforces the argument
I made many months ago for a less 'defaulty’ interface to shift/reset
and a clearer type (a disjoint union) to describe the result of a call
to reset. Using the alternative interface in the module delimcc
(in the genutils pack), your original code would be written with the
calc/2 and continue/1 replaced as follows:

calc(A, B) :- expr_to_goal(A, B, G), run(G).

run(G) :- reset(G, Status), continue(Status).
continue(done).
continue(susp(A->B, Cont)) :-
      expr_to_goal(A, B, G),
      run(G), run(Cont). % could also be run((G,Cont)).

Note how now there is a clear separation between the two possible
results from reset/2, and it is impossible to mistakenly start processing the
‘Ball’ A->B and the continuation Cont if there was no call to shift/1. Neither
is there a need to have a special clause like continue(0) :- !.

cheers,
Samer


signature.asc

Kuniaki Mukai

unread,
Sep 10, 2018, 10:43:59 PM9/10/18
to Samer Abdallah, SWI-Prolog

Hi Samer, 

Thank you for your long comment. I am digesting it.   I feel sorry to taking your time
for an exercise of mine on reset/shift.   I can agree with most of your comments. In fact, I  have noticed already most of them.
My codes is a kind of translation from what I did without delimited continuation  on my bekind-ekind block.
It is,  in a sense,  how to combine basic term rewriting systems which has its own normal form including integers,
though I  got only partial results. I would like to review if I have time with a new tool of reset/shift.

Btw   reset/2 is not found in the manual, but only reset/3. Is it a typo ?

On Sep 11, 2018, at 5:15, Samer Abdallah <seventy...@gmail.com> wrote:


calc(A, B) :- expr_to_goal(A, B, G), run(G).

run(G) :- reset(G, Status), continue(Status).
continue(done).
continue(susp(A->B, Cont)) :-
      expr_to_goal(A, B, G),
      run(G), run(Cont). % could also be run((G,Cont)).

Kuniaki Mukai

Kuniaki Mukai

unread,
Sep 11, 2018, 5:34:00 AM9/11/18
to SWI-Prolog, Samer Abdallah
In spite of Samer’s kind worrying about my codes on shift/reset,
I can not stop writing another similar example, but on lists
this time. Of course, I agree that shift/rest is not necessary
for what is intended by this example, but I, whose
background is only the descrption on reset/shift in the manual, like it.
I can not explain why I like it,  but am just enjoying. Sorry for this noisy.

% ?- continue_list(comp_list([a,b]+[c,d], X)).
%@ X = [a, b, c, d] .
% ?- continue_list(comp_list(-([a,b]+[c,d]), X)).
%@ X = [d, c, b, a] .
% ?- continue_list(comp_list(([a,b,c]-[c,d]), X)).
%@ X = [a, b] .
% ?- continue_list(comp_list(-(-([a,b]+[c,d])), X)).
%@ X = [a, b, c, d] .

%
comp_list(X, X):- is_list(X), !.
comp_list(X, Y):- shift(X -> [Op|Args]),
maplist(comp_list, Args, Vs),
G =..[Op|Vs],
call(G, Y).
%
continue_list(true).
continue_list(0).
continue_list(Cont):- reset(Cont, Ball, Cont0),
( var(Ball)
->  continue_list(Cont0)
; Ball = (L->[P0|Args0]),
L =.. [P|Args],
internal_op(P, Args, P0, Args0),
continue_list(Cont0)
).

%
internal_op(+, Args, append, Args).
internal_op(*, Args, intersection, Args).
internal_op(-, Args, reverse, Args):- length(Args, 1), !.
internal_op(-, Args, subtract, Args).

-K.M.

Samer Abdallah

unread,
Sep 11, 2018, 5:49:14 AM9/11/18
to Kuniaki Mukai, SWI-Prolog
Hi Kuniaki,

This is a good use of delimited continuations, to provide a computational
effect sometimes called ‘yield’. The code that uses yield is then made to look
like some sort of lazy, potentially infinite stream. If you know Python, it’s a
bit like the system of generators and iterables. There are some writings
around these topics here:
http://okmij.org/ftp/continuations/generators.html
http://okmij.org/ftp/Streams.html

One of the powerful features of delimited continuations is the ability to
decouple the request for a computational effect (like yield) from the particular
strategy chosen to implement it. With that in mind, I’ve taken the liberty of
paraphrasing your code to make that separation clearer:

First, provision of the effect, via shift, but using a predicate yield/1 to make
the purpose clear at the point of requesting the effect, and the functor yield/1
to make the meaning of the effect (the ‘ball’ term) explicit:

yield(X) :- shift(yield(X)).

Now a ‘generator’: a piece of code that *uses* the effect, without caring how
it is provided:

ints_from(M):- yield(M), N is M+1, ints_from(N).

Now a ‘handler’ next/3. It takes a generator Gen1, calls it inside a delimited
context, and decides how to handle the ‘yield’ effect. In this case, it suspends the
computation, returning the yielded value and a new generator representing
the rest of the stream, which is just the continuation.

:- use_module(library(delimcc)).

% next(-X:A, +Gen1:generator(A), -Gen2:generator(A)).
next(X, Gen1, Gen2) :- reset(Gen1, susp(yield(X), Gen2)).

With this we can write code which processes an infinite stream without caring
about delimited continuation or any implementation details:

for_each(P, Str1) :- next(X, Str1, Str2), call(P, X), for_each(P, Str2).
print_and_pause(X) :- writeln(X), get_single_char(0’;).

?- for_each(print_and_pause, ints_from(3)).
3 % key press ;
4 % key press ;
5 % key press ;
6 % any other key press
false.

Because of the argument order chosen for next/3, it works nicely with foldl/3:

?- foldl(next, Ints, ints_from(1), _).
Ints = [] ;
Ints = [1] ;
Ints = [1, 2] ;
Ints = [1, 2, 3] .

Note that I’ve gone from referring to these things as ‘generators’ to ‘streams’.
This is subtle but meaningful change of emphasis: ‘generator’ implies a piece of
code that uses yield/1 to spit out values. The generator knows it’s a generator and
the effect handler knows that the piece of code is a generator. But ‘stream’ refers
to some arbitrary representation of a sequence. for_each/2 does not care about
generators or delimited continuations. It only needs to be able to split a stream into
a head and tail using next/3.

We can also write next_prime//2 DCG style, as a stateful computation where
the state is a generator representing the sequence of integers still to be tested:

next_prime(Ps, P) -->
next(J),
( {member(Q, Ps), mod(J, Q) =:= 0}
-> next_prime(Ps, P)
; {P = J}
).

This reads, ‘to get the next prime P following primes Ps, get the next integer J. If J is divisible
by any prime in Ps, discard it and try again (recursively); otherwise, J is
the next prime.’ The DCG notation takes care of managing the stream of integers, but apart
from that , this is the same as your implementation.

Next, print_primes_endless/1 is basically the same as yours, the only difference being perhaps
one of emphasis, that the Gen argument is treated as an opaque term representing a stream,
without knowing or caring that it is a continuation — all that matters is that next/3 gets the next
element

print_primes_endless :- print_primes([], ints_from(2)).
print_primes(Ps, Ints1) :-
next_prime(Ps, P, Ints1, Ints2), print_and_pause(P),
print_primes([P|Ps], Gen2).

Finally, for primes/1 we can go back to DCG style to hide the generators altogether:

primes(Ps) :- primes(Ps, [], ints_from(2), _).
primes([], _) --> [].
primes([Q|Qs], Ps) --> next_prime(Ps, Q), primes(Qs, [Q|Ps]).

A different approach is to take the idea that a generator is a lazy stream, and try to implement
some generic stream processing operations on it, like map or filter. Let’s try to implement
something like this piece of Haskell:

primes = filterPrime [2..]
where filterPrime (p:xs) = p : filterPrime [x | x <- xs, x `mod` p /= 0]

Let’s start by writing filter_stream/2. I did not think too carefully about this, in particular I
did not think about delimited continuations or what might be happening with shift and
reset. I just thought, 'let’s write a generator that extracts values from another generator
and yields them conditionally’. Therefore, I took it as a good sign that it just worked:

filter_stream(P, Str1) :-
next(X, Str1, Str2),
(call(P, X) -> yield(X); true),
filter_stream(P, Str2).

Trying it out:

not_divisible_by(P, X) :- mod(X, P) =\= 0.

?- for_each(print_and_pause, filter_stream(not_divisible_by(3), ints_from(1))).
1 % press ;
2
4
5
7 % press <return>
false.

Now we can write primes/0 as a generator:

primes :- filter_prime(ints_from(2)).
filter_prime(Str1) :-
next(P, Str1, Str2), yield(P),
filter_prime(filter_stream(not_divisible_by(P), Str2)).

?- foldl(next, Primes, primes, _).
Primes = [] ;
Primes = [2] ;
Primes = [2, 3] ;
Primes = [2, 3, 5] .


cheers,
Samer
signature.asc

Samer Abdallah

unread,
Sep 11, 2018, 5:50:10 AM9/11/18
to Kuniaki Mukai, SWI-Prolog
On 11 Sep 2018, at 03:43, Kuniaki Mukai <kuniak...@gmail.com> wrote:


Hi Samer, 

Thank you for your long comment. I am digesting it.   I feel sorry to taking your time
for an exercise of mine on reset/shift.   I can agree with most of your comments. In fact, I  have noticed already most of them.
My codes is a kind of translation from what I did without delimited continuation  on my bekind-ekind block.
It is,  in a sense,  how to combine basic term rewriting systems which has its own normal form including integers,
though I  got only partial results. I would like to review if I have time with a new tool of reset/shift.

Btw   reset/2 is not found in the manual, but only reset/3. Is it a typo ?

Oh - no, reset/2 is in the module delimcc which is in the pack genutils.

Samer.
signature.asc

Samer Abdallah

unread,
Sep 11, 2018, 9:26:00 AM9/11/18
to Kuniaki Mukai, SWI-Prolog
Hi Kuniaki,

Yes indeed, managing state is another good application of delimited
continuations. A few comments below..

> On 7 Sep 2018, at 13:21, Kuniaki Mukai <kuniak...@gmail.com> wrote:
>
> % ?- continue(test([a, a, b, b, c, c], R), []).
>
> %@ catch_ball(update(a,1),[],[a-2])
> %@ catch_ball(update(a,2),[a-2],[a-3])
> %@ catch_ball(update(b,1),[a-3],[b-2,a-3])
> %@ catch_ball(update(b,2),[b-2,a-3],[b-3,a-3])
> %@ catch_ball(update(c,1),[b-3,a-3],[c-2,b-3,a-3])
> %@ catch_ball(update(c,2),[c-2,b-3,a-3],[c-3,b-3,a-3])
> %@ catch_ball(update(c,3),[c-3,b-3,a-3],[c-4,b-3,a-3])
> %@ R = [1, 2, 1, 2, 1, 2] .
>
> test([], []):-!.
> test([A|As], [X|Bs]):- shift(update(A, X)),
> test(As, Bs).
>
> %
> continue(true, _).
> continue(0, _).
> continue(Cont, S):- reset(Cont, Ball, Cont0),
> catch_ball(Ball, S, S0),
> writeln(catch_ball(Ball, S, S0)),
> continue(Cont0, S0).

This is another case where the defaulty interface to reset/3
has lead to a potential problem — here if Cont completes without
calling shift/1, then Cont0 is unified with 0 and Ball is left as
a variable. This means that catch_ball/3 is called with an unintended
mode, and by a quirk of its internal implementation, succeeds
unifying Ball with update(c,3) and applying the action ‘increment counter
c’, even though no such action was requested. It so happens that
the incorrect resulting S0 is discarded on calling continue(0, S0), so
it doesn’t matter, but if continue had been written to return the final state
S0 (ie like a real DCG) that final state would be wrong.

Let’s re-write it using reset/2 from delimcc. First, the effect:

% effect is 'get and increment named counter’, like a++ in C.
get_and_inc(A, X) :- shift(get_and_inc(A, X)).

Now the handler:

:- use_module(library(delimcc)).

run_multi_counter(G, S) :- reset(G, Status), continue(Status, S).

continue(done, _).
continue(susp(get_and_inc(Nm, Val), Cont), S1) :-
(select(Nm-Val, S1, S2) -> true; Val=1, S1=S2),
succ(Val, NewVal),
run_multi_counter(Cont, [Nm-NewVal|S2]).

In continue/2, it is not possible to process Nm and Val unless
shift was called in the goal G. Finally, the test

?- run_multi_counter(maplist(get_and_inc,[a,a,b,b,c,c], R), []).
R = [1, 2, 1, 2, 1, 2].

This idea can be generalised into a handler of stateful computations
that allows any binary predicate to be applied to the state and that
also returns the final state, just like phrase/3 or call_dcg/3. The handler
itself can be written very neatly in DCG notation:

app(P) :- shift(app(P)). % should apply P with state, as in call(P,S1,S2).

run_state(G) —> {reset(G, Status)}, continue(Status).
continue(done) —> [].
continue(susp(app(P), Cont)) —> call_dcg(P), run_state(Cont).

Using app/1, we can define a typical set of state manipulating predicates:

get(S, S, S). put(S, _, S). % orinary predicates
get(S) :- app(get(S)). put(S) :- app(put(S)). % effectful, must be used inside run_state//1

app_and_get(P, S) :- app(P), get(S).

?- run_state(maplist(app_and_get, [succ, succ, put(10), plus(-1)], States), 0, Final).
States = [1, 2, 10, 9],
Final = 9.

You can build a multi-state handler on top of this without worrying about shift
and reset any more, eg using rbtrees instead of lists, for faster access when there
are many accumulators (but probably slower for just a few):

:- use_module(library(rbutils), [rb_app/3, rb_add/4]) % in pack genutils

app(Nm, P) :- app(rb_app(Nm, P)). % apply P to named accumulator
new(Nm, Val) :- app(rb_add(Nm, Val)). % add a new accumulator, fail if already exists.

run_multi_state(Goal) :- rb_empty(E), run_state(Goal, E, _).

Let’s use it to build a little sequence transducer that repeats each element twice.

repeat(_).
repeat(T) :- call(T), repeat(T).

transduce(Transducer, In, Out) :-
new(in, In), new(out, Out), % set up two accumulators
repeat(Transducer),
get(in, []), get(out, []). % unify final state of accumulators with [], like phrase/2

double :- app(in, [X]), app(out, [X,X]). % using DCG notation for action on in/out

?- run_multi_state(transduce(double, [1,2,3], Out)).
Out = [1, 1, 2, 2, 3, 3] .

cheers,
Samer

> %
> catch_ball(update(A, X), S, [A-X0|S0]):- select(A-X, S, S0),!,
> X0 is X+1.
> catch_ball(update(A, 1), S, [A-2|S]).
>

Samer Abdallah

unread,
Sep 11, 2018, 10:59:08 AM9/11/18
to Kuniaki Mukai, SWI-Prolog
Hi Kuniaki,

It is interesting to see how different computational strategies combine,
and, as you have found, shift and reset work fine in DCGs.

> On 7 Sep 2018, at 20:03, Kuniaki Mukai <kuniak...@gmail.com> wrote:
>
> Hi,
>
> expand_phrase(shift(X), M, {shift(Y)}, P, Q) :-
> expand_arg(X, M, Y, P, Q).

I’m not sure this is the best place to do goal expansion (which I presume is
necessary when using lambda terms). The argument to shift/1 is in general
just an ordinary term — it is only in specific use cases, like the state handler,
that the argument should be interpreted as a callable goal. Therefore, the
goal expansion should be associated with the request for that specific effect,
not in the general case of shift/1.

For example, we can try to implement your enumeration DCG on top of the
run_state//1 effect handler in my last email, where effect app/1 applies a
binary predicate to the current state. What I omitted from the code then
is a rather important meta predicate declaration:

:- meta_predicate app(2).

This means that any argument to app/1 is treated as a callable goal and
subject to goal expansion. Using library(yall) for the lambda term, we can write

paired_with_next(N-X) --> [X], {app({N}/[N,M] >> succ(N,M))}.

The lambda term is correctly detected and expanded

?- listing(paired_with_next).
paired_with_next(B-A, [A|D], C) :-
app('__aux_yall_ce92ad53c0b3088f3d54f8cfcc5b16c315e825d7'(B)),
C=D.

The expansion is associated with app/1 not shift/1, which I think is better.
I tried this using library(pac) for the lambda term, but the lambda was not expanded.
Perhaps pac does not interact with the meta predicate declarations to determine
what to expand? Alternatively, we can avoid the lambda and write

paired_with_next(N-X) --> [X], {app((get(N),succ))}.

The enumerator is then

enumerated(Pairs) --> foldl(paired_with_next, Pairs).
?- run_state(phrase(enumerated(Zip), [a,b,c]), 1, _).
Zip = [1-a, 2-b, 3-c] .

We can also play with the nesting of the DCG and state frameworks.
Let’s add a state handler that can be used inside a DCG:

run_state_in_dcg(G, Inner1, Inner2, Outer1, Outer2) :-
run_state(call_dcg(G, Outer1, Outer2), Inner1, Inner2).

Then add a version of enumerated that introduces a locally scoped state:

enumerated_local(Pairs) --> run_state_in_dcg(foldl(paired_with_next, Pairs), 1, _).

And compare: state outside, DCG inside

?- run_state(phrase((enumerated(Z1), enumerated(Z2)), [a,b,c]), 1, _).
Z1 = [],
Z2 = [1-a, 2-b, 3-c] ;
Z1 = [1-a],
Z2 = [2-b, 3-c] ;
Z1 = [1-a, 2-b],
Z2 = [3-c] ;
Z1 = [1-a, 2-b, 3-c],
Z2 = [] ;
false.

DCG outside, state inside:

?- phrase((enumerated_local(Z1), enumerated_local(Z2)), [a,b,c]).
Z1 = [],
Z2 = [1-a, 2-b, 3-c] ;
Z1 = [1-a],
Z2 = [1-b, 2-c] ;
Z1 = [1-a, 2-b],
Z2 = [1-c] ;
Z1 = [1-a, 2-b, 3-c],
Z2 = [] ;
false.

>
> continue(true, _).
> continue(0, _).
> continue(Cont, S):- reset(Cont, Ball, Cont0),
> ( var(Ball) -> true
> ; call(Ball, S, S0),
> continue(Cont0, S0)
> ).

I see you are detecting the no-shift condition with var(Ball) before
doing anything else, which avoids the potential for problems I described
in my previous email. In this case there is no need for the first two clauses.

Thank you for exploring this subject on the mailing list and providing
sample code to provoke discussion.

cheers,
Samer

Kuniaki Mukai

unread,
Sep 11, 2018, 11:03:13 AM9/11/18
to Samer Abdallah, SWI-Prolog

Hi Samer,

Thank you for tutorial on delimited continuation, which are more advanced ones
than I had expected. I will take time for reading them again and visit
urls of articles you gave.

I have several questions related on delimited continuations,
but it will be probably in one month that I will ask you,
because I enjoy to find answers myself. it is related to (left object/ right object) of categorical
programming by Hagino. I have to remember ….

- K.M.

Samer Abdallah

unread,
Sep 11, 2018, 11:29:07 AM9/11/18
to Kuniaki Mukai, SWI-Prolog

> On 11 Sep 2018, at 10:33, Kuniaki Mukai <kuniak...@gmail.com> wrote:
>
> In spite of Samer’s kind worrying about my codes on shift/reset,
> I can not stop writing another similar example, but on lists
> this time. Of course, I agree that shift/rest is not necessary
> for what is intended by this example, but I, whose
> background is only the descrption on reset/shift in the manual, like it.

Perhaps I was a bit harsh in my first email when I said you don’t need
delimited continuations to do this! Indeed you don’t, but what you can
do that is quite powerful is provided different implementions of the
same effects by running the same code in different dynamic contexts
(ie in different effect handlers).

To illustrate, consider the idea that certain operators such as +, * etc
forming an algebra can be defined on different spaces, eg, integers,
real values, vectors, polynomials, infinite series, etc. In terms of the
basic operators one might wish to implement generic computations
that work for all instances of the algebra, eg, for floating point K but
arbitrary X1 and X2 in some vector space over the reals, we can
interpolate between X1 and X2:

interpolate(K1, X1, X2, Y) :-
K2 is 1-K,
mul(K1, X1, KX1),
mul(K2, X2, KX2),
add(KX1, KX2, Y).

To make this work polymorphically for arbitrary types there are a
number of strategies. In Haskell there are type classes. In OCaml,
there are parameterised modules. In Prolog, we could add a explicit
module parameter like this:

interpolate(M, K1, X1, X2, Y) :-
K2 is 1-K,
M:mul(K1, X1, KX1),
M:mul(K2, X2, KX2),
M:add(KX1, KX2, Y).

Alternatively, without modifying the original interpolate/4, we could
implement add/3 and mul/3 as effects:

add(X,Y,Z) :- shift(add(X,Y,Z)).
mul(X,Y,Z) :- shift(mul(X,Y,Z)).

and then provide different effect handlers for different types, eg

run_floats(G) :- reset(G, Status), cont_floats(Status).
cont_floats(done).
cont_floats(susp(Op, Cont)) :- using_floats(Op), run_floats(Cont).

using_floats(add(X,Y,Z)) :- Z is X + Y.
using_floats(mul(X,Y,Z)) :- Z is X * Y.

run_lists(G) :- reset(G, Status), cont_lists(Status).
cont_lists(done).
cont_lists(susp(Op, Cont)) :- using_lists(Op), run_lists(Cont).

using_lists(add(X,Y,Z)) :- maplist(plus, X, Y, Z).
using_lists(mul(X,Y,Z)) :- maplist(times(X), Y, Z).
times(X,Y,Z) :- Z is X*Y.

There is some obvious duplication here, so better still:

run_vectors(Space, G) :- reset(G, Status), cont(Space, Status).
cont(Space, susp(Op, Cont)) :- using(Space, Op), run_vectors(Space, Cont).
cont(_, done).

using(floats, add(X,Y,Z)) :- Z is X + Y.
using(floats, mul(X,Y,Z)) :- Z is X * Y.
using(lists, add(X,Y,Z)) :- maplist(add_x, X, Y, Z).
using(lists, mul(X,Y,Z)) :- maplist(mul_x(X), Y, Z).

add_x(X,Y,Z) :- Z is X+Y.
mul_x(X,Y,Z) :- Z is X*Y.

Also, it is now clear from the implementation of cont/2 that calls to shift
in this effect handler cannot be reduced to ordinary Prolog calls, since the
results depend on the ‘side’ information in Space which is passed around
the call to reset.

?- run_vectors(floats, interpolate(0.2, 10, 20, X)).
X = 18.0 .

?- run_vectors(lists, interpolate(0.2, [10,5,2], [20,30,10], X)).
X = [18.0, 25.0, 8.4] .

cheers,
Samer

Paulo Moura

unread,
Sep 11, 2018, 12:02:52 PM9/11/18
to Samer Abdallah, Kuniaki Mukai, SWI-Prolog
Hi,
In Logtalk (and thus in SWI-Prolog and 11 other systems) there are parametric objects and you can do:

:- object(foo(_M_)).

interpolate(K1, X1, X2, Y) :-
K2 is 1-K,
_M_::mul(K1, X1, KX1),
_M_::mul(K2, X2, KX2),
_M_::add(KX1, KX2, Y).

Sorry, couldn't resist :-) Curious if this is similar to OCaml parameterized modules.

Btw, thanks to you and Kuniaki for the interesting discussion on delimited continuations!

Cheers,
Paulo

Proof Easel

unread,
Sep 11, 2018, 7:13:52 PM9/11/18
to SWI-Prolog
Hi Samer Abdallah,

Thank you for the example and test case.

Works also with functions on Prolog dicts, both in SWI-Prolog
and Jekejeke Prolog 1.3.0 (not yet released, still testing):

?- X = floats{}.interpolate(0.2, 10, 20).
X = 18.0.

?- X = vector{}.interpolate(0.2, [10,5,2], [20,30,10]).

X = [18.0, 25.0, 8.4].

File 1: base.pl  (algorithm base class)
=============================
:- module(base, [interpolate/5]).
M.interpolate(K1, X1, X2) := Y :-
K2 is 1-K1,
KX1 = M.mul(K1, X1),
KX2 = M.mul(K2, X2),
Y = M.add(KX1, KX2).

File 2: floats.pl (subclass for floats)
===========================
:- module(floats, []).
:- reexport(base).
_.add(X,Y) := Z :- Z is X + Y.

_.mul(X,Y) := Z :- Z is X * Y.

File 3: vector.pl (subclass for vector)
===========================
:- module(vector, []).
:- reexport(base).
_.add(X, Y) := Z :- maplist(plus, X, Y, Z).

plus(X, Y, Z) :- Z is X+Y.

_.mul(X, Y) := Z :- maplist(times(X), Y, Z).

times(X, Y, Z) :- Z is X*Y.

For screenshot and source code see also here:
https://gist.github.com/jburse/fb77f6ffd4139ad6bd5392ba3937c18f#gistcomment-2704160

To be continued ...

Proof Easel

unread,
Sep 11, 2018, 7:25:41 PM9/11/18
to SWI-Prolog
Remark: No shallow copying of any Prolog dicts,
and everything based on ISO core module standard,
"no hands inheritance", means nothing extra done

for inheritance. You can do listing in SWI-Prolog 7 to
see what code is generated. You will see the SWI-Prolog
7 code, in Jekejeke Prolog 1.3.0 the code is slightly

differently generated:

File 1: base.pl:
============
?- listing(interpolate/5).
base:interpolate(K1, X1, X2, M, Y) :-
    K2 is 1-K1,
    '.'(M, mul(K1, X1), A),
    KX1=A,
    '.'(M, mul(K2, X2), B),
    KX2=B,
    '.'(M, add(KX1, KX2), C),
    Y=C,
    true.

File 2: floats.pl
?- listing(add/4).
==============
floats:add(X, Y, _, Z) :-
    Z is X+Y,
    true.
?- listing(mul/4).
floats:mul(X, Y, _, Z) :-
    Z is X*Y,
    true.

File 3: vector.pl
=============
vector:add(X, Y, _, Z) :-
    maplist(plus, X, Y, Z),
    true.
vector:mul(X, Y, _, Z) :-
    maplist(times(X), Y, Z),
    true.

Samer Abdallah

unread,
Sep 12, 2018, 6:13:34 AM9/12/18
to Proof Easel, SWI-Prolog
Hi Jan,

Interesting - I didn’t know you could do this. I haven’t really
looked into dicts very much. I guess this means that dicts are increasingly
object-like. However, in this case, the dict is still acting as no more than
a proxy for the module name. If you want to add a new operation, say, diff/2:

diff([X0|Xs], DXs) :- foldl(delta, Xs, DXs, X0).
delta(X1, DX, X0, X1) :- sub(X1, X0, DX).
sub(X,Y,Z) :- mul(-1.0, Y, NegY), add(X,NegY,Z).

you can’t write truly polymorphic predicates as above. Either you have
to add a dict (or a module) as a parameter to *each* predicate above, to
act as a witness to the type of the vectors, like this:

diff(M, [X0|Xs], DXs) :- foldl(delta(M), Xs, DXs, X0).
delta(M, X1, DX, X0, X1) :- sub(M, X1, X0, DX).
sub(M, X, Y, M.add(X, M.mul(-1.0,Y))).

*or* you have to add all these predicates in the to base module as dict functions, 
so the base module cannot be considered a stable piece of library code that one 
can build on (rather than modify)  to add functionality. Basically, it leads towards
the kind of OO mess (in this case, complicated and restrictive class hierarchies with 
inheritance) that OO methods can lead to unless used very carefully.

cheers,
Samer.





signature.asc

Proof Easel

unread,
Sep 12, 2018, 6:26:07 AM9/12/18
to SWI-Prolog
The example has two forms of "proxing" if you prefer this name.
The two forms are:

- Implizit super class delegation, this is the more object oriented part:
      When you invoke XXX.interpolate() the ISO core module standard,
      via reexport/1 finds the base class. And executes the method there.
      The method there has a parameter M, so that it knows the original
      receiver object and its class.
        
- Explicit class delegation, this is the less object oriented part:
     When then M.add and M.mul are invoked, also based on the
     ISO core module standard, you can lookup how Jan Wielemaker
     implemented ('.')/2, this doesn't need to go along the reexport/1
     chain, it finds the method directly.

Concerning OO-mess. You are right! Thats why I preventively
wrote on comp.lang.prolog:

   "Disclaimer: Dont take the example as an advice to provide problem
   solution pairs in any context. I adopted the example only to test
   the no hands inheritance building block. It takes a seasoned

   software architect to analyse & design larger systems based on a
   variety of object oriented modelling techniques, that matches a
   set of requirements."
 
You can take "architect" verbatim as building a house. I was myself
a software architect for >15 years, first Credit Suisse, than EAWAG,
then IBM Switzerland, before I created my company XLOG.

You might end up with something like:
http://www.dasverruecktehaus-bispingen.de/

To be continued ...

Samer Abdallah

unread,
Sep 12, 2018, 6:28:42 AM9/12/18
to Paulo Moura, Kuniaki Mukai, SWI-Prolog

> On 11 Sep 2018, at 17:02, Paulo Moura <pjlm...@gmail.com> wrote:
>
>
> In Logtalk (and thus in SWI-Prolog and 11 other systems) there are parametric objects and you can do:
>
> :- object(foo(_M_)).
>
> interpolate(K1, X1, X2, Y) :-
> K2 is 1-K,
> _M_::mul(K1, X1, KX1),
> _M_::mul(K2, X2, KX2),
> _M_::add(KX1, KX2, Y).
>
> Sorry, couldn't resist :-) Curious if this is similar to OCaml parameterized modules.

Yes, it does seem to be. How does one use the foo object in practice? Presumably
you instantiate it with the require parameter? Can you ‘import’ the predicates of foo,
or do you have to explicitly refer to the foo instance on each call to interpolate/4?

Samer
signature.asc

Proof Easel

unread,
Sep 12, 2018, 6:34:42 AM9/12/18
to SWI-Prolog
Hi,

Some further remarks.

1) Unused This:
    Of course it can happen that a method doesn't needs its this
    argument. In Python the "this" is also explicit, in other languages
    its implicit. The method is basically happy in that was called,
    and doesn't make use of this. This is seen in the code here:

   
    _.add(X,Y) := Z :- Z is X + Y.
_.mul(X,Y) := Z :- Z is X * Y.

    The unused this is seen by the underscore.  But in the function
    on dicts example we cannot go Logtalk-ish, where everything
    is a method, and you cannot mix static and dynamic methods,
    but you can drop the "this".

2) Mixing Static & Dynamic Methods:
    So we cannot drop the "this", but we can mix static & dynamic
    methods. Dynamic methods are introduced by the Jan Wielemaker
    operator (:=)/2, static methods are simple predicates. You
    see this here:
        
     /* dynamic */

_.add(X, Y) := Z :- maplist(plus, X, Y, Z).
     _.mul(X, Y) := Z :- maplist(times(X), Y, Z).
     /* static */

plus(X, Y, Z) :- Z is X+Y.
     times(X, Y, Z) :- Z is X*Y.

    So you buy into a non Logtalk-ish way of mixing dynamic and static
    methods. Static method is the Java name, its synonymous to ordinary
    predicates.

3) Module Scattering
    I have introduced in Jekejeke Prolog the concept of local modules.
    So its possible to implement the example all in one Prolog text.
    Maybe this is also possible in SWI-Prolog 7, I dunno.

    If I have time, I will provide the example in one Prolog text with
    locale modules. This is probably the last test case I need, before
    I can go public with my functions on Prolog dicts.

Have Fun!

Proof Easel

unread,
Sep 12, 2018, 6:54:43 AM9/12/18
to SWI-Prolog
Probe your knowledge of functions on dicts Quiz!

Did you know the following? You can write:

M.interpolate(K1, X1, X2) := Y :-
K2 is 1-K1,
KX1 = M.mul(K1, X1),
KX2 = M.mul(K2, X2),
Y = M.add(KX1, KX2).

Also as follows:

M.interpolate(K1, X1, X2) := Y :-
K2 is 1-K1,
    Y = M.add(M.mul(K1, X1), M.mul(K2, X2)).

But a more OO-ish design would probably do something
totally different. Like SmallTalk like expressions, I guess
sympy and Julia has this,

and then you can go with the types/classes of X1 and X2,
to drive the interpolation. Whats a SmallTalk like expression?
Well in SmallTalk you could "evaluate":

      3 max: 5

And get the result 5. But it will send a message "max:"
with argument 5 to the number 3. With SmallTalk like
expressions you get much more concise code,

and you don't see any parameters anymore. Jan Wielemakers
Prolog dicts don't implement SmallTalk like expressions.
My CAS system implememts SmallTalk like expressions.

There are a lot of loose ends that need to be tied together.
The problem is that the Prolog community is brain washed
by Log-Nonsense-Talk, which can even not mix

static and dynamic definitions in the same code. Also
I find old stack overflow posts, that claim Prolog dicts cannot
do OO. I rediscovered the contrary, although there are old

papers that show similar ideas already. Basically the idea is
found here: Reexport/1 and (:)/2 proof of concept: Who
observed it first? I find for example

    A. Pineda and M. Hermenegildo. O'ciao:
   An Object Oriented Programming Model for
   (Ciao) Prolog. Facultad de Informática, UPM, July 1999. 
   http://oa.upm.es/14764/1/HERME_TCREP_ANDMANS_1999-3.pdf

Paulo Moura

unread,
Sep 12, 2018, 9:48:31 AM9/12/18
to Samer Abdallah, Kuniaki Mukai, SWI-Prolog
Hi Samer,

> On 12 Sep 2018, at 11:28, Samer Abdallah <seventy...@gmail.com> wrote:
>
>
>> On 11 Sep 2018, at 17:02, Paulo Moura <pjlm...@gmail.com> wrote:
>>
>>
>> In Logtalk (and thus in SWI-Prolog and 11 other systems) there are parametric objects and you can do:
>>
>> :- object(foo(_M_)).
>>
>> interpolate(K1, X1, X2, Y) :-
>> K2 is 1-K,
>> _M_::mul(K1, X1, KX1),
>> _M_::mul(K2, X2, KX2),
>> _M_::add(KX1, KX2, Y).
>>
>> Sorry, couldn't resist :-) Curious if this is similar to OCaml parameterized modules.
>
> Yes, it does seem to be. How does one use the foo object in practice? Presumably
> you instantiate it with the require parameter? Can you ‘import’ the predicates of foo,
> or do you have to explicitly refer to the foo instance on each call to interpolate/4?

After writing the object code to a source file (adding a public/1 directive for the interpolate/4 predicate) and loading it, a sample query would be:

?- foo(vector)::interpolate(...).

Note that there isn't any instantiation process. There's also no import of predicates in the sense of modules (none is needed). Technically, the foo/1 object is (i.e. plays the role of) a prototype. It is also a static object (which is the default). But using a parameter is just one of the possible programming idioms. A arguably more sensible one would be to have a root or base prototype declaring and possible also defining predicates such as interpolate/4, which would them simply send to "self" the mul/3 and add/3 messages. Something like:

:- object(math).

:- public(interpolate/4).
interpolate(K1, X1, X2, Y) :-
K2 is 1-K,
::mul(K1, X1, KX1),
::mul(K2, X2, KX2),
::add(KX1, KX2, Y).

:- protected([mul/3, add/3]).

:- end_object.

And then extend this object with derived prototypes like floats or vector. E.g.

:- object(vector, extends(math)).

% vector specific code

:- end_object.

This solution still isn't ideal, however, as nothing prevents the user of trying a query such as:

?- math::interpolate(...).

The query will simply fail (as per closed-world assumption) but is still meaningless. But the fix is simple. Just use a category (think e.g. a mixin) instead of an object for math and have vector and floats import it (we can only send messages to objects). Either solution requires dynamic binding (at the cost of +1 logic inference compared with static binding) as the the parameter or self are only know at runtime. Both solutions are also fully portable. The full code is at:

https://gist.github.com/pmoura/c04dedc03ce4e0e820e82bd1afc56dc8

To run it, either:

$ swipl
?- pack_install(logtalk).
?- use_module(library(logtalk)).
?- {interpolation}.

Or preferably if you use on the Logtalk installers (the pack is only advised for deployment):

$ swilgt
?- {interpolation}.

For using e.g. the vector object from other objects (or categories), you can either write:

..., vector::interpolate(...), ...

or add a uses/2 directive and use the predicate directly:

:- uses(vector, [interpolate/4]).

..., interpolate(...), ...

For using the object from within a module, currently you need to write vector::interpolate(...) but it would be possible to support uses/2 directives inside modules. Note that ::/2 calls from modules are compiled (and optimized if you turn on the Logtalk optimize flag).

As a last note, regarding performance, I get:

?- time(true).
% 3 inferences, 0.000 CPU in 0.000 seconds (68% CPU, 300000 Lips)
true.

?- time(X = floats{}.interpolate(0.2, 10, 20)).
% 33 inferences, 0.000 CPU in 0.000 seconds (73% CPU, 1736842 Lips)
X = 18.0.

?- set_logtalk_flag(optimize, on).
true.

?- {interpolation}.
% [ /Users/pmoura/Desktop/interpolation.lgt loaded ]
% (0 warnings)
true.

?- time(floats::interpolate(0.2, 10, 20, I)).
% 52 inferences, 0.000 CPU in 0.000 seconds (81% CPU, 1106383 Lips)
I = 18.0.

?- time(floats::interpolate(0.2, 10, 20, I)).
% 8 inferences, 0.000 CPU in 0.000 seconds (72% CPU, 285714 Lips)
I = 18.0.

Top-level calls imply dynamic binding for ::/2 calls. But the generalized form of the query is then cached (that's why the number of inferences goes down from 52 for the first call to 8 for subsequent calls). E.g.

?- time(floats::interpolate(0.4, 14, 27, I)).
% 8 inferences, 0.000 CPU in 0.000 seconds (67% CPU, 333333 Lips)
I = 21.8.

Cheers,
Paulo

Kuniaki Mukai

unread,
Sep 12, 2018, 10:13:07 AM9/12/18
to Paulo Moura, SWI-Prolog
Hi Paulo, 

I am not a logtalk user,  I  have followed your instruction  
on the  emacs buffer:


On Sep 12, 2018, at 22:48, Paulo Moura <pjlm...@gmail.com> wrote:

$ swipl
?- pack_install(logtalk).
?- use_module(library(logtalk)).
?- {interpolation}.


?- pack_install(logtalk).
%@ % Contacting server at http://www.swi-prolog.org/pack/query ... ok
<snip>
%@ Install "logtalk-3.20.0.tgz" (4,536,162 bytes) Y/n? 
%@ true.

?- use_module(library(logtalk)).
%@ true.

?- {interpolation}.
%@ ERROR: file `interpolation' does not exist,

Where is the file ? Am I missing as usual ?

-K.M.















Paulo Moura

unread,
Sep 12, 2018, 11:12:39 AM9/12/18
to SWI-Prolog

> On 12 Sep 2018, at 11:54, Proof Easel <janb...@easel.ch> wrote:
>
> There are a lot of loose ends that need to be tied together.
> The problem is that the Prolog community is brain washed
> by Log-Nonsense-Talk, which can even not mix
>
> static and dynamic definitions in the same code.

You call Logtalk repeatedly (here and in other public forums) nonsense, a fraud, useless, and now with brain washing powers. You seem unable to write about your alternative without trashing Logtalk. For anyone wondering why so much hate for Logtalk, it seams to have started after I informed Jan Burse back in 2016 that I was unable to support his Prolog system, Jekejeke, until several standards compliance issues were fixed. Or maybe something else triggered it. My brain washing powers don't seem to extend to mind reading. Thankfully.

Here's some facts:

1. The existence of Logtalk doesn't affect in any way what your alternative solution can and cannot do.

2. Any alternative should be able to stand on its own.

3. Alternatives are a good thing. They embed different design decisions. They give users choices.

4. Anyone serious about using the best tool for a job does due diligence on alternatives.

5. Giving examples where your alternative doesn't work, or doesn't appear to work, is valid criticism. Nothing prevents you from showing a solution and proving that it can be done in a sensible way. Examples also help understand any limitations of different alternatives.

6. Claiming that the community is brain washed is seeing the people in the community in lesser terms.

7. Logtalk respect by the community results notably from (1) people recognizing continuous hard work freely shared for anyone to benefit; (2) people recognizing the role Logtalk played in the convergence of Prolog systems and on official and de facto standardization. No brain washing involved.

8. Repeatedly making strong but baseless claims that Logtalk doesn't support basic functionality found in most OO languages only backfires. There's plenty of documentation and examples. Questions can always be asked. But I confess that I have long lost patience for your ramblings.

9. Repeatedly insulting people and their work only results in a toxic environment and serves no purpose other than killing any chance of enlightening and fruitful discussions. Btw, I didn't get you (temporarily) banned from this mailing list. You did it to yourself. Apparently you have learned nothing as you continue to do it elsewhere.

As a last note, thanks for all your publicity on Logtalk. There's true in the saying that there isn't such thing as bad publicity!

Paulo Moura

unread,
Sep 12, 2018, 11:14:58 AM9/12/18
to Kuniaki Mukai, SWI-Prolog
Hi,
You can download it from:

https://gist.github.com/pmoura/c04dedc03ce4e0e820e82bd1afc56dc8

Note that the {}/1 shortcut accepts both relative and absolute file paths, with or without extension.

Cheers,
Paulo

-----------------------------------------------------------------
Paulo Moura
Logtalk developer


Proof Easel

unread,
Sep 12, 2018, 11:24:22 AM9/12/18
to SWI-Prolog
The license terms of Jekejeke Prolog do not allow to run Logtalk on
top of it. The license terms of the runtime library don't allow to use
it for such purposes as Logtalk where a different toplevel of essential

already existing functionality is shown. Sorry, thats not permitted.
I will probable do some changes to the license terms in the next
days. Logtalk solves a few problems, in my opinion also creates

a lot of problems. And it doesn't address problems that are more
pressing than those that it addresses. Nothing to do with trigger,
these are just facts...

Proof Easel

unread,
Sep 12, 2018, 11:28:54 AM9/12/18
to SWI-Prolog
Examples it doesn't address: Logtalk is NOT based on function
expansion. Only goal and term expansion. Logtalk stops at
the rest. It uses meta-predicate declarations,

but only for goals and terms. This wont possibly give a good
integration of functional programming and object oriented
programming. Prolog dicts are more interesting in this respect...

Kuniaki Mukai

unread,
Sep 12, 2018, 11:38:50 AM9/12/18
to Paulo Moura, SWI-Prolog
Hi Paulo,  Thanks again.

On Sep 13, 2018, at 0:14, Paulo Moura <pjlm...@gmail.com> wrote:

Hi,

On 12 Sep 2018, at 15:13, Kuniaki Mukai <kuniak...@gmail.com> wrote:

Hi Paulo, 

I am not a logtalk user,  I  have followed your instruction  
on the  emacs buffer:

On Sep 12, 2018, at 22:48, Paulo Moura <pjlm...@gmail.com> wrote:

$ swipl
?- pack_install(logtalk).
?- use_module(library(logtalk)).
?- {interpolation}.


?- pack_install(logtalk).
%@ % Contacting server at http://www.swi-prolog.org/pack/query ... ok
<snip>
%@ Install "logtalk-3.20.0.tgz" (4,536,162 bytes) Y/n? 
%@ true.

?- use_module(library(logtalk)).
%@ true.

?- {interpolation}.
%@ ERROR: file `interpolation' does not exist,

Where is the file ? Am I missing as usual ?

You can download it from:

https://gist.github.com/pmoura/c04dedc03ce4e0e820e82bd1afc56dc8

Note that the {}/1 shortcut accepts both relative and absolute file paths, with or without extension.

I see.  Now it works, and I feel good !

?- {'~/interpolation'}.

?- time(floats::interpolate(0.2, 10, 20, I)).
%@ % 93 inferences, 0.000 CPU in 0.000 seconds (86% CPU, 2447368 Lips)
%@ I = 18.0.

?- time(floats::interpolate(0.4, 14, 27, I)).
%@ % 9 inferences, 0.000 CPU in 0.000 seconds (65% CPU, 692308 Lips)
%@ I = 21.8.

- K.M.

Paulo Moura

unread,
Sep 12, 2018, 11:43:48 AM9/12/18
to Kuniaki Mukai, SWI-Prolog
Forgot to mention (sorry): anyone using the pack version of Logtalk, please see the pack specific readme at:

http://www.swi-prolog.org/pack/list?p=logtalk

The pack contains all files found in the sources distribution/installers but the pack directory structure sort of hides them from the user. The specific readme shows a simple way of accessing the included documentation, examples, etc.

Samer Abdallah

unread,
Sep 12, 2018, 12:16:01 PM9/12/18
to Paulo Moura, Kuniaki Mukai, SWI-Prolog
Hi Paulo,

Thanks for explanation and examples.

> On 12 Sep 2018, at 14:48, Paulo Moura <pjlm...@gmail.com> wrote:
>
> Hi Samer,
>
>> On 12 Sep 2018, at 11:28, Samer Abdallah <seventy...@gmail.com> wrote:
>>
>>
>>> On 11 Sep 2018, at 17:02, Paulo Moura <pjlm...@gmail.com> wrote:
>>>
>>>
>>> In Logtalk (and thus in SWI-Prolog and 11 other systems) there are parametric objects and you can do:
>>>
>>> :- object(foo(_M_)).
>>>
>>> interpolate(K1, X1, X2, Y) :-
>>> K2 is 1-K,
>>> _M_::mul(K1, X1, KX1),
>>> _M_::mul(K2, X2, KX2),
>>> _M_::add(KX1, KX2, Y).
>>>
>>> Sorry, couldn't resist :-) Curious if this is similar to OCaml parameterized modules.
>>
>> Yes, it does seem to be. How does one use the foo object in practice? Presumably
>> you instantiate it with the require parameter? Can you ‘import’ the predicates of foo,
>> or do you have to explicitly refer to the foo instance on each call to interpolate/4?
>
> After writing the object code to a source file (adding a public/1 directive for the interpolate/4 predicate) and loading it, a sample query would be:
>
> ?- foo(vector)::interpolate(...).
>
> Note that there isn't any instantiation process. There's also no import of predicates in the sense of modules (none is needed). Technically, the

I like the parameterised object approach — it’s the closest thing to ML parameterised modules I’ve seen yet.
It also means it’s easy to add more generic computations like the diff/2 example, simply by adding another
parameterised object to implement them:

:- object bar(_M_).
diff(Xs,Dxs) :- % etc..

foo doesn’t care about bar, bar doesn’t care about foo, floats and lists don’t care about foo
and bar. Everything is loosely coupled and I can put them together however I want.

> foo/1 object is (i.e. plays the role of) a prototype. It is also a static object (which is the default). But using a parameter is just one of the possible programming idioms. A arguably more sensible one would be to have a root or base prototype declaring and possible also defining predicates such as interpolate/4, which would them simply send to "self" the mul/3 and add/3 messages. Something like:
> :- object(math).
>
> :- public(interpolate/4).
> interpolate(K1, X1, X2, Y) :-
> K2 is 1-K,
> ::mul(K1, X1, KX1),
> ::mul(K2, X2, KX2),
> ::add(KX1, KX2, Y).
>
> :- protected([mul/3, add/3]).
>
> :- end_object.
>
> And then extend this object with derived prototypes like floats or vector. E.g.
>
> :- object(vector, extends(math)).
>
> % vector specific code
>
> :- end_object.
>
> This solution still isn't ideal, however, as nothing prevents the user of trying a query such as:

This inheritance based approach is what I don’t like. The math base class,
which is the carrier of the interpolate predicate, is baked into the subclasses. What if
I want to add diff/2 now? Do I have to add it to math? Can math somehow be an open
structure that I can add to without touching the original source code of math?

cheers,
Samer


Paulo Moura

unread,
Sep 12, 2018, 12:50:34 PM9/12/18
to Samer Abdallah, Kuniaki Mukai, SWI-Prolog
Hi Samer,

> On 12 Sep 2018, at 17:15, Samer Abdallah <seventy...@gmail.com> wrote:
>
> Hi Paulo,
>
> Thanks for explanation and examples.

You're most welcome.
:-)

>> foo/1 object is (i.e. plays the role of) a prototype. It is also a static object (which is the default). But using a parameter is just one of the possible programming idioms. A arguably more sensible one would be to have a root or base prototype declaring and possible also defining predicates such as interpolate/4, which would them simply send to "self" the mul/3 and add/3 messages. Something like:
>> :- object(math).
>>
>> :- public(interpolate/4).
>> interpolate(K1, X1, X2, Y) :-
>> K2 is 1-K,
>> ::mul(K1, X1, KX1),
>> ::mul(K2, X2, KX2),
>> ::add(KX1, KX2, Y).
>>
>> :- protected([mul/3, add/3]).
>>
>> :- end_object.
>>
>> And then extend this object with derived prototypes like floats or vector. E.g.
>>
>> :- object(vector, extends(math)).
>>
>> % vector specific code
>>
>> :- end_object.
>>
>> This solution still isn't ideal, however, as nothing prevents the user of trying a query such as:
>
> This inheritance based approach is what I don’t like. The math base class,
> which is the carrier of the interpolate predicate, is baked into the subclasses.

Please note that there are no classes in the example. In the gist I posted, we only have a category and two independent prototypes *virtually* importing it (i.e. without any code duplication). You can even compile the prototypes first and the category last (the compiler will give you a warning but that just because it haven't seen the referenced category when compiling the prototypes). Category importing is not an inheritance mechanism. It's a composition mechanism.

> What if
> I want to add diff/2 now? Do I have to add it to math?

No.

> Can math somehow be an open
> structure that I can add to without touching the original source code of math?

Categories, like protocols, are expected to be functionally cohesive. A gentle introduction to both protocols and categories is:

https://github.com/LogtalkDotOrg/logtalk3/tree/master/examples/planets

If you have a new set of predicates with diff/2 and closely related ones, you can simply defined another category and then have any objects (playing the role of prototypes or classes) importing it. A good example of using categories for composition to avoid the pitfalls of inheritance is:

https://github.com/LogtalkDotOrg/logtalk3/tree/master/examples/points

Somehow related, you can patch any (loaded) object without touching is code by defining a complementing category. You may recognize the original concept taking from Objective-C here. Patching can be forbidden in a global or per object basis. When allowed, you can restrict patching to only adding new predicates or allow it to also fix existing ones. See e.g.

https://github.com/LogtalkDotOrg/logtalk3/tree/master/examples/complements

Did I answered your question satisfactorily?

Proof Easel

unread,
Sep 12, 2018, 3:10:53 PM9/12/18
to SWI-Prolog
In my opinion, one Brainwashi thing, is that one cannot
do listing of the code that Logtalk generates. At least the
last time I used the Logtalk pillow command line for

SWI-Prolog. If one checks the SWI-Prolog adapter
one sees that there some special $hide directive in the
code of this adapter.

So for a "community" it is really difficult to understand what
Logtalk does and to give feedback where Logtalk could
improve. For example this code from Paulo Moura (see
link below) here:

:- object(vector, imports(math)).
add(X, Y, Z) :- apply:maplist(plus, X, Y, Z).
plus(X, Y, Z) :- Z is X+Y.
mul(X, Y, Z) :- apply:maplist(times(X), Y, Z).
times(X, Y, Z) :- Z is X*Y.
:- end_object.

It would be very interesting to see what it EXACTLY
does. Does it have a "this" somewhere. Yes or no. If not,
its just a module no Logtalk needed, and its not the
same code as my first take on the Samer example.

If yes, can we do the following:
- mix static and non-static

If there is a "this" somewhere, how does the code
know that plus/3 and times/3 is static, and that add/[?3,4,5?]
and mul[?3,4,5?] are dynamic.

This is what I mean by brainwash. The brainwash is not that
some members of the community are lesser. The brainwash is
that Logtalk is not transparent,

makes it not easy for the end-user to see what is going
on. On the other hand SWI-Prolog 7 Prolog dicts, and
functions on Prolog dicts are fairly transparent,

listing works, and its much easer to deal with them. Even
Picat is better in this respect. I recently asked whether they
can show the B-Prolog compile. And the answer was

yes this and that command. How do I list the above example?
What is the resulting code? What are the Open Source measures,
against Brainwash here?

Am Mittwoch, 12. September 2018 15:48:31 UTC+2 schrieb Paulo Moura:
https://gist.github.com/pmoura/c04dedc03ce4e0e820e82bd1afc56dc8

Proof Easel

unread,
Sep 12, 2018, 3:20:21 PM9/12/18
to SWI-Prolog
Hi,

If Logtalk were more transparent, it would more count as
a honorable contribution to the community, by the efforts
of one person. At the moment its not clear what your code

does, and whether it has anything to do with my example.
For example if the code had really a "this", they the
call from dynamic to stattic would be, thats how you call

so called plain code:

    {maplist(plus, X, Y, Z)}

but instead I see something like:

   apply:maplist(times(X), Y, Z).

So I guess its either plain-plain, or the non-plain-non-plain.
Neither is what my example did. non-plain-non-plain could
give a an unwanted performance penalty.

Currently I see 10-20 downloads of your Logtalk pack,
and its a one man show. If it would be more transparent,
with a listing/[0,1] that works, and is also recommended

and explained, then there would be maybe more downloads,
and maybe more contributions. A system doesn't get
more transparent and more collaborators by providing

more examples. In the age of open source, a system gets
more transparent and more collaborators by showing
each and every artefact that is consumed or generated.

Hope this helps...

Proof Easel

unread,
Sep 12, 2018, 3:42:23 PM9/12/18
to SWI-Prolog
I also posted my plead on comp.lang.prolog:

Preventing further Brainwash by Logtalk
https://groups.google.com/forum/#!msg/comp.lang.prolog/s6-QPBp3z3w/0BrddKoqBAAJ

Proof Easel

unread,
Sep 12, 2018, 4:11:19 PM9/12/18
to SWI-Prolog
The $hide in Logtalk probably prevents that Logtalk is legal
on top of some Prolog systems. It depends what license the
Prolog system itself has.

For example in a GNU Prolog, the idea is that GNU Prolog
is open source, and derivative work as well, that the GNU
License is adopted.

But the $hide in Logtalk doesn't make it open source so much.
Maybe the situation is different for LGPL. GNU Prolog
has a dual license:

http://www.gprolog.org/manual/html_node/gprolog003.html

The $hide maybe a smelly license wise... Not sure. The
situation is probably not easy to analyse in a few minutes,
without some literature search...

Paulo Moura

unread,
Sep 13, 2018, 7:34:50 AM9/13/18
to SWI-Prolog
(single mail reply to several mails)

> On 12 Sep 2018, at 16:24, Proof Easel <janb...@easel.ch> wrote:
>
> The license terms of Jekejeke Prolog do not allow to run Logtalk on
> top of it. The license terms of the runtime library don't allow to use
> it for such purposes as Logtalk where a different toplevel of essential
>
> already existing functionality is shown. Sorry, thats not permitted.
> I will probable do some changes to the license terms in the next
> days.

First time I hear about licenses incompatibilities. Back then (2015/0909), you send me a mail with a "Changed my Mind: Some help for an Adapter for Logtalk" subject. Did the license changed in relevant ways between 2015 and today? As I wrote, the reasons why a working adapter could not be coded as due to standards compliance issues. Notably, lack of support for initialization/1 directives and the definition of clauses for dynamic directives in source files. Implementing a Prolog system and making it compliant with official and de facto standards is hard work. It may be the case that those issues are already solved.

On 12 Sep 2018, at 16:28, Proof Easel <janb...@easel.ch> wrote:

> Examples it doesn't address: Logtalk is NOT based on function
> expansion. Only goal and term expansion.

Logtalk is *not* based on the term-expansion mechanism (that would make it non-portable!).

On 12 Sep 2018, at 20:10, Proof Easel <janb...@easel.ch> wrote:

> In my opinion, one Brainwashi thing, is that one cannot
> do listing of the code that Logtalk generates. At least the
> last time I used the Logtalk pillow command line for
>
> SWI-Prolog.

Logtalk is implemented using a *compiler* (and, of course, a runtime) that generates *intermediate* Prolog code. This Prolog code is the Logtalk equivalent to e.g. Java byte-code. The last step of the compiler is to call the Prolog compiler on this intermediate code. What happens then obviously depends on the used Prolog system.

You can examine to your heart content this intermediate Prolog code by simply turning of the Logtalk "clean" flag and opening the generated files with the intermediate Prolog code in a text editor. Note that fully understanding the intermediate code requires understanding the runtime. In the same exact sense that understanding byte-code requires understanding the byte-code interpreter.

> If one checks the SWI-Prolog adapter
> one sees that there some special $hide directive in the
> code of this adapter.

The $hide/1 directive is a *SWI-Prolog proprietary directive*. It's only used in SWI-Prolog (!) to hide generated entity internal tables and auxiliary predicates and avoid any chance of listing polluting output. I.e. to hide from the listing/0-1 predicates code that is *not* user code.

The listing/0-1 predicates are *convenient* predicates that list mainly user's *source* code. It avoids a trip to the text editor. True, if the *source* code is subject to term- or goal-expansion, then the expansion results can also be listed as the term-expansion mechanism is a *source-to-source* transformation. As already explained, Logtalk is not implemented using term-expansion. The *intermediate* Prolog code generated by the Logtalk compiler is not *source* code.

> So for a "community" it is really difficult to understand what
> Logtalk does and to give feedback where Logtalk could
> improve.

Taking as example the SWI-Prolog community, plenty of people contribute without caring about how this C-based compiler works, which linked lists, or arrays, or registers are used internally. They also don't go around crying foul as they look in horror to the VM instructions (i.e. the equivalent to the *intermediate* Prolog code generated by Logtalk) that result from compiling Prolog source files claiming that Prolog is difficult to understand.

Understanding a programming language requires people willing to run examples, try some code, read user and reference manuals, ask questions, interact with fellow programmers. Learning a language doesn't force people to understand VMs, GC details, and other low-level machinery. For those that are into those details, there's other source material, including for Logtalk. People that want to contribute at this level already mastered the language.

> At the moment its not clear what your code
>
> does, and whether it has anything to do with my example.
> For example if the code had really a "this", they the
> call from dynamic to stattic would be, thats how you call
>
> so called plain code:
>
> {maplist(plus, X, Y, Z)}
>
> but instead I see something like:
>
> apply:maplist(times(X), Y, Z).
>
> So I guess its either plain-plain, or the non-plain-non-plain.
> Neither is what my example did.

I long lost track of how many times I tried to explain to you the role of the {}/1 control construct. You claim that Logtalk is difficult to understand while blatantly ignoring its documentation. The rebuttal to your never-ending bogus rants is in the user and reference manuals. In plain sight. You know, reading instead of guessing. You can also simply ask.

> non-plain-non-plain could
> give a an unwanted performance penalty.

You're the only person responsible for the performance of your examples.

> Currently I see 10-20 downloads of your Logtalk pack,
> and its a one man show.

The pack is only advised for *limited application deployment* scenarios. Stats for the Logtalk pack mean very little. The SWI-Prolog packs page is *not* the Logtalk download website! Most people download an *installer* for their operating-system. An increasing number of people simply clone its repo. Several third-party software websites also distribute Logtalk.

Btw, most Prolog systems, Prolog libraries, ..., are one man show. Should we all give up? How many people in your team developing you Prolog system? How many core maintainers in SWI-Prolog?

> If it would be more transparent,
> with a listing/[0,1] that works, and is also recommended

The listing/0-1 predicates are not a tool of *transparency*. They are a tool of *convenience*. Transparency comes from fully specified languages. From full documentation on syntax and semantics. You know, the kind of documentation that is bundled with the Logtalk distribution.

It would be useful for Logtalk to provide listing predicates. But that would not give you what you're looking for because what you're looking for is based on misconceptions about how Logtalk works. When it was the last time anyone claimed that SWI-Prolog is not transparent as you cannot easily list generated VM instructions?

> and explained, then there would be maybe more downloads,
> and maybe more contributions.

Dozens of people clone and download Logtalk installers *daily*. Thanks for your concerns nevertheless.

> A system doesn't get
> more transparent and more collaborators by providing
>
> more examples. In the age of open source, a system gets
> more transparent and more collaborators by showing
> each and every artefact that is consumed or generated.

People that genuinely care about open source success don't go around trashing it repeatedly. By routinely writing "Log-Nonsense-Talk" you show your true colors.

On 12 Sep 2018, at 21:11, Proof Easel <janb...@easel.ch> wrote:

> The $hide in Logtalk probably prevents that Logtalk is legal
> on top of some Prolog systems.

As explained, the $hide/1 directive is a SWI-Prolog proprietary directive. Concerns about "on top of some Prolog systems" are meaningless.

> It depends what license the
> Prolog system itself has.
>
> For example in a GNU Prolog, the idea is that GNU Prolog
> is open source, and derivative work as well, that the GNU
> License is adopted.
>
> But the $hide in Logtalk doesn't make it open source so much.

Logtalk is distributed under the Apache License 2.0, which also covers the code generated by the Logtalk compiler.

I see myself as a *guest* in this mailing list. This kind of discussions don't belong here. Hence my single reply to several mails. There are other public forums where subscribers of this mailing list can chose to visit and then reply or skip these topics.

Proof Easel

unread,
Sep 13, 2018, 9:45:37 AM9/13/18
to SWI-Prolog
Paulo Moura be careful before you offer services
in the following form "You can also simply ask.".
This requires two things:
- You need to invest time, here I am asking something
  new from another thread
- You need to answer the question that was asked,
  and not something else

I am asking something else from another thread,
because the solution of the other thread is closer

to what Samer wants. Samer wrote:

"you can’t write truly polymorphic predicates as above."

In object oriented programming, you can write
kind of polymorphic methods. So this is the context,
nothing extra ordinary, just something in the present

context. So the question and the focus for your
service public in the spirit of your "You can also simply
ask."
, would be, without changing the topic now.

Can you show a Logtalk solution for this problem:

    floats:   
  X.add(Y) := Z :- Z is X + Y.
X.mul(Y) := Z :- Z is X * Y.

vector:
X.add(Y) := Z :- maplist(plus, X, Y, Z).

plus(X, Y, Z) :- Z is X+Y.
  X.mul(Y) := Z :- maplist(times(Y), X, Z).

times(X, Y, Z) :- Z is X*Y.

For the full function on dicts solution here:
https://groups.google.com/d/msg/swi-prolog/V-SJW_syu8E/8nND3VTxBwAJ

What is the Logtalk code solution, and
what is the Logtalk Prolog generated code?

Paulo Moura

unread,
Sep 13, 2018, 10:47:15 AM9/13/18
to SWI-Prolog

> On 13 Sep 2018, at 14:45, Proof Easel <janb...@easel.ch> wrote:
>
> Paulo Moura be careful before you offer services
> in the following form "You can also simply ask.".

Too late for *you*. Sure, you can simply ask. But you will no longer get any answers. After calling Logtalk a fraud any leftover goodwill is gone and you're out of free support. You're also out of payed support or any other form of support offered by me. If Samer is curious about polymorphism in Logtalk, I'm sure is perfectly capable of asking himself.

Proof Easel

unread,
Sep 13, 2018, 11:03:23 AM9/13/18
to SWI-Prolog
Why do you write "You can also simply ask." if you don't mean
it? Anyway, I can do the example myself in Logtalk, and also
list the produced Prolog code myself as well, even if I need

to hack the SWI-Prolog 7 adapter for this purpose. There is no
need to rely on somebody who is up to some cat and mouse game.
This only proves my brainwashing intransparency claim with

everything around Logtalk, and that it can take any technical
critique. I am also not interested in some infantile Logtalk games,
who is a groupie of Logtalk and who is not a groupie of Logtalk,

and doesn't deserve attention. Thats just not professional, and
shows your mental immaturity and that you have something
to hide. Period.

Proof Easel

unread,
Sep 13, 2018, 11:04:08 AM9/13/18
to SWI-Prolog
Corr.:
everything around Logtalk, and that it cannot take any technical

critique. I am also not interested in some infantile Logtalk games,

Paulo Moura

unread,
Sep 17, 2018, 8:58:25 PM9/17/18
to SWI-Prolog
Hi,

Last week I wrote:

> On 13 Sep 2018, at 12:34, Paulo Moura <pjlm...@gmail.com> wrote:
>
> Logtalk is distributed under the Apache License 2.0, which also covers the code generated by the Logtalk compiler.

Above, the second part of the sentence is, of course, incorrect. Sorry about that; I was thinking about the runtime. The license doesn't not extend to the code generated when user compiles his/her source files. Your source files, your license, your copyright. More information about licensing is available at:

https://github.com/LogtalkDotOrg/logtalk3/wiki/Licensing

The wiki page contains a link to a useful resource on combining open source software. In the particular case of running Logtalk with SWI-Prolog, we have Simplified BSD license + Apache License 2.0 ---> Apache License 2.0. This will be the license for the combined SWI-Prolog + Logtalk runtimes when packaging an application.

Cheers,
Paulo

Kuniaki Mukai

unread,
Sep 18, 2018, 3:09:03 PM9/18/18
to SWI-Prolog

After execises on shift/reset I have come to a predicate continue/3 below
as a most general form of wrapping predicates of shift/reset
(delimited continuation) of SWI-Prolog.

%% continue(+Goal, +State, +Action) is det.
%
% Action : Ball x State -> State (Monoid action).
% Goal: (a member of a fixed monoid viewed as identity action on tates.
% Goal and Ball together are seen as monoid action on State.

continue(true, _, _).
continue(0, _, _).
continue(Cont, S, A):- reset(Cont, Ball, Cont0),
( var(Ball)
-> continue(Cont0, S, A)
; call(A, Ball, S, S0),
continue(Cont0, S0, A)
).

Provided that my interpreation on shift/reset is correct and complete,
I hope there is no redundancy in the coninue/3 for extended
DCG with states. Although now I know Samer did the same thing more
elegantly, I like this crude wrppaer. I am still on the way for the delimited continuation.

Here is an example use of continue/3, which I posted in
a slightly different form, which is now defined as this:

continue_with_zip(G, S):- continue(G, S, zip).


Other predicates are the same as posted before but listed here
for convenience.

% Example use of continue/3.
numbering(As, As) --> [].
numbering([X-C|As], Bs) --> [X], shift(count(X-C)),
numbering(As, Bs).
%
count(K-C, S, [K-C0|S0]):- zip(K-C, S, S1),
zip(delete(K), S1, S0),
C0 is C + 1.

%
pred_numbering(As, As) --> [].
pred_numbering([X-N|As], Bs) --> [X],
shift(pred([X, N],
([S, [X-N|S0]]:- select(X-N0, S, S0), !,
N is N0 + 1)
& ([S, [X - N|S]]:- N = 1))),
pred_numbering(As, Bs).

% Access to a zip (assoc list).
zip(true, Zip, Zip):-!.
zip(clear, _, []):-!.
zip(K-V, Zip, Zip0):-!,
( memberchk(K-U, Zip) -> U = V, Zip0 = Zip
; V = 1, Zip0 = [K-1|Zip]
).
zip(set(K, V), Zip, [K-V|Zip0]):-!,
( select(K-_, Zip, Zip0) -> true
; Zip0 = Zip
).
zip(delete(K), Zip, Zip0):-!, ( select(K-_, Zip, Zip0); Zip0 =Zip ), !.
zip((A,B), Zip, Zip0):-!, zip(A, Zip, Zip1), zip(B, Zip1, Zip0).
zip(S, Zip, Zip):- is_list(S), !, subset(S, Zip).
zip(C, Zip, Zip0):- call(C, Zip, Zip0).

% ?-continue_with_zip(numbering(Zip, [], [a,a], []), []).
%@ Zip = [a-1, a-2]
% ?-continue_with_zip(numbering(Zip, [], [a, a, b, b, b], []), []).
%@ Zip = [a-1, a-2, b-1, b-2, b-3] .
% ?-continue_with_zip(pred_numbering(Zip, [], [a, b, c], []), 1).
%@ Zip = [a-1, b-1, c-1] .
% ?-continue_with_zip(pred_numbering(Zip, [], [a, b, a, b, c], []), []).
%@ Zip = [a-1, b-1, a-2, b-2, c-1] .
% ?-continue_with_zip(shift((clear, set(a, 1), a-X, set(a, 2), a-Y)), []).
%@ X = 1,
%@ Y = 2
% ?-continue_with_zip(shift(((clear, set(a, 1), a-X))), []).
%@ X = 1 .
% ?-continue_with_zip((shift((clear, set(a, 1), a-X)), Two = two, writeln(hello), shift((set(a, Two), a-Y))), []).
%@ hello
%@ X = 1,
%@ Two = Y, Y = two .
% ?-continue_with_zip((shift(clear), numbering(Zip, [], [a, a, b, b, b], [])), X).
%@ Zip = [a-1, a-2, b-1, b-2, b-3]
% ?-continue_with_zip((shift(pred([_, [a-2, b-2]])), numbering(Zip, [], [a, a, b, b, b], [])), X).
%@ Zip = [a-2, a-3, b-2, b-3, b-4] .


-K.M.


Kuniaki Mukai

unread,
Sep 23, 2018, 10:53:26 PM9/23/18
to SWI-Prolog

I have been playing around shift/reset (delimitted continuation),
and replaced some part of my ZDD library (pack/pac/misc/zdd.pl)
which access to ZDD working enviroments, an extendible array with rehash
for instance, via globals with shift/reset codes as listed below.
The zdd codes consists of about 4,000 lines including comment lines.

It was rather suprsing that the replacement goes smoothly without
any difficulty, though not yet finished.
(I am on the way of trip with a mac book pro).
Now for me the usual prolog goals look like DCG phrases
with respect to shift/reset. Aside a possible marginal overheads,
I think delimited continuation surely leads prologers to more advanced
stages for problem solving like DCG used to give us.In a sense, Shift/reset is
an extended (cured form of ) DCG.

The following is a series of patches to the codes for ZDD
processing replacing use of globals with shift/reset,
which should be are all clear because of purpose of use of globals
like "file control block" (too old analogy now ?)


:- meta_predicate continue(:).
continue(C):- init_state(S), continue(C, S).

%
continue(true, _).
continue(0, _).
continue(Cont, S):- reset(Cont, Ball, Cont0),
( var(Ball)
-> continue(Cont0, S)
; call(Ball, S),
continue(Cont0, S)
).

%
get_extra(Extra):- shift(get_extra_(Extra)).
get_extra_(Extra, s(_, Extra)).
get_extra(K, V):- shift(get_extra(K, V)).
get_extra(K, V, s(_, Assoc)):- memberchk(K-V, Assoc).

%
set_extra(Extra):- shift(set_extra_(Extra)).
set_extra_(Extra, S):- setarg(2, S, Extra).

set_extra(K, V):- shift(set_extra(K, V)).
set_extra(K, V, S) :- S = s(_, Assoc),
( select(K-_, Assoc, Assoc0)
-> setarg(2, S, [K-V|Assoc0])
; setarg(2, S, [K-V|Assoc])
).
%
varnum(D):- shift(get_extra(varnum, D)).
%
getzdd(X, Y):- shift(getzdd_(X, Y)).
%
getzdd_(X, Y, s(Z, _)):- arg(3, Z, Array),
arg(X, Array, Y).
%
getzdd(X, Y, Z):- arg(3, Z, Array), arg(X, Array, Y).
%
is_fos_mode:- shift(is_fos_mode).
is_fos_mode(s(_, Assoc)) :- memberchk(mode-fos, Assoc).
%
is_sat_mode:- shift(is_sat_mode).
is_sat_mode(s(_, Assoc)) :- memberchk(mode-sat, Assoc).
%
set_fos_mode :- shift(set_fos_mode).
set_fos_mode(S) :- S = s(_, Assoc),
( select(mode-_, Assoc, Assoc0)
-> setarg(2, S, [mode-fos|Assoc0])
; setarg(2, S, [mode-fos|Assoc])
).
%
set_sat_mode :- shift(set_sat_mode).
%
set_sat_mode(S) :- S = s(_, Assoc),
( select(mode-_, Assoc, Assoc0)
-> setarg(2, S, [mode-sat|Assoc0])
; setarg(2, S, [mode-sat|Assoc])
).
%
cofact(X, Y):- shift(cofact_(X, Y)).
%
cofact_(X, Y, s(Z, _)):- cofact(X, Y, Z).
%
peek(S):- shift(peek(S)).
%
peek(S, S).
%
vector(X):- get_vector(X).
%
get_vector(X):- shift(get_vector(X)).
%
get_vector(Vec, s(#(_,_,Vec),_)).
%
memo(X):- ( X = Key-_ ; Key = X ), !,
shift(H),
memo_update(Key, H, X).

-K.M.

Kuniaki Mukai

unread,
Sep 24, 2018, 3:44:18 PM9/24/18
to SWI-Prolog
After playing around shift/reset, I gradually came to
an interpretaion of the following sentence, which is
a mystery for me for a long time.

"While Haskell has monad, Prolog has DCG."

Let m be a monad in Haskell : ma -> (a -> mb) -> mb

My observation is that a monad looks like a type or behaviour of shift/rest.
In other words, m is implemented in Prolog based on the shift/reset roughly
as follows.

Throwing a ball (e.g. an assignment below).

shift(f: a->mb)

Catching ball:

reset(C, Ball, C0)

After catching, define a ball as a monoid action: for example.

act(f: a->mb, x, y):- define x:ma |-> y:mb from the given f.
(e.g. promote assignment f to a substitution F below).

A typical example is a case in which ma is the set of terms
generated from a set a of terms. Then with f: a-> mb being an assignment,
the ball action F might be defined as subsitution derived from
the assignment f as in our familiar prolog programming.

I am not a Haskell programmer at all, but I only heard in the literature
that the haskell monad is interpreted as the well-known Kleisli construction.

Maybe I have simplified monad too much, and look into a ceil of Haskell
with the shift/reset as a thin straw. I would be glad
if someone kindly add comments on this note or correct it.

-K.M.

Kuniaki Mukai

unread,
Sep 24, 2018, 8:43:55 PM9/24/18
to Kuniaki Mukai, SWI-Prolog
I have checked replaced globals in my ZDD library with delimited
continuations. Still I have no problems with these replacements.

Now I like delimited continuation more than globals, because
delimited continuation looks more close to DCG.

Below are several queries on time measuring, which seems to show that
overheads on delimitied continuation is not so bad as I worried
about compared with globals, but I am not sure on this.

time of shift/reset on my macbook pro.
—————————————————

?- time(continue(n_queens(12, C))).
% 59,748,185 inferences, 10.285 CPU in 10.386 seconds (99% CPU, 5809182 Lips)
C = 14200 .

?- time(continue(n_queens(13, C))).
% 346,705,117 inferences, 63.040 CPU in 63.430 seconds (99% CPU, 5499767 Lips)
C = 73712 .

% ?- time(continue((problem(7, P), solc(P, X)))). % problem/2 by Jan Burse.
%@ % 402,855,063 inferences, 94.539 CPU in 95.797 seconds (99% CPU, 4261267 Lips)
%@ X = 1451520 .

I will compare these times with those on my iMac after back home.

- K.M.

Kuniaki Mukai

unread,
Oct 1, 2018, 3:07:37 AM10/1/18
to SWI-Prolog
Hi,

Here is a rough comparison on time measurement
between delimitted continuations and globals. The former uses shift/reset
instead of accessing globals with n_getval/n_setval

queries shift/reset globals
------------------------------------------------------------------
problem(7) 85.549 CPU 37.181 CPU
n_queens(13) 54.777 CPU 49.437 CPU
domino(14,14) 57.396 CPU 42.518 CPU

Altnough this table shows that globals are more faster than shift/reset,
it is marginal for me to prefer the latter.

-K.M.

>

Reply all
Reply to author
Forward
0 new messages