using dict to pass arguments to some predicates

109 views
Skip to first unread message

Dan

unread,
Aug 31, 2018, 9:53:33 AM8/31/18
to SWI-Prolog
Hello, 

I have a predicate in a core routine that has quite a list or arguments (11 right now), and i just had to add the 11th whch rippled through quite a number of other predicates in the core system -- its not a terrible thing -- took me perhaps 15 min to change; but i am wondering whether it might make sense to use a dict structure, python like, to such predicates -- which would enable adding arguments as needed. 

What would be the performance penalty over regular argument binding. 

Is such an approach advisable. 

All arguments are always ground. 

thank you,

Dan

Paulo Moura

unread,
Aug 31, 2018, 10:12:10 AM8/31/18
to Dan, SWI-Prolog
Hi Dan,

> On 31 Aug 2018, at 14:53, Dan <gros...@gmail.com> wrote:
>
> Hello,
>
> I have a predicate in a core routine that has quite a list or arguments (11 right now), and i just had to add the 11th whch rippled through quite a number of other predicates in the core system -- its not a terrible thing -- took me perhaps 15 min to change; but i am wondering whether it might make sense to use a dict structure, python like, to such predicates -- which would enable adding arguments as needed.
>
> What would be the performance penalty over regular argument binding.

You loose first (and multiple argument indexing) for the predicate. How significant a performance penalty that would be depends on the number of clauses for the predicate(s).

> Is such an approach advisable.
>
> All arguments are always ground.

Logtalk parametric objects using parameter variables can be a good solution for this scenario if those arguments are shared/passed to most of the predicates. The parameters are *logical variables* shared by all object predicate clauses. A simple example:

:- object(foo(_A1_,_A2_)).

:- public(bar/1).
bar(S) :-
S is _A1_ * _A2_.

:- end_object.

Then:

?- foo(2,3)::bar(S).
S = 6.

Adding a new parameter only requires changing the object identifier and the clauses that need to access that new parameter with all remaining code staying unaltered. E.g.

:- object(foo(_A1_,_A2_,_A3_)).

bar(S) :-
S is _A1_ * _A2_.

baz(D) :-
D is 2 * _A3_.

:- end_object.

As the names of the parameters are chosen by you, the code stays readable and the predicate clauses, as in dicts, don't have to worry about parameter positions. As the Logtalk compiler translates the access to parameters into inlined head unifications, performance is quite good.

Cheers,
Paulo

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



Samer Abdallah

unread,
Aug 31, 2018, 10:53:22 AM8/31/18
to Dan, SWI-Prolog

> On 31 Aug 2018, at 14:53, Dan <gros...@gmail.com> wrote:
>
> Hello,
>
> I have a predicate in a core routine that has quite a list or arguments (11 right now), and i just had to add the 11th whch rippled through quite a number of other predicates in the core system -- its not a terrible thing -- took me perhaps 15 min to change; but i am wondering whether it might make sense to use a dict structure, python like, to such predicates -- which would enable adding arguments as needed.

It can be helpful to think about grouping the parameters into a smaller number of data structures.
Think about which parameters tend to move around together, which parameters can be ignored
together depending on conditions which emerge. Think about factorising the predicate into smaller
parts that you can then compose. I would guess that it is relatively unusual to have a computation
which needs 11 parameters which fly around independently inside a predicate and is so tightly bound
that it can’t be broken apart.

One thing I often do is draw a dataflow graph for the predicate: draw a circle for each argument to
the predicate and each variable inside it, draw a box for each subgoal, and draw lines between each
variable and each subgoal it participates in. Redraw it until the layout is as neat as possible.
Then look for clusters in the graph or groupings between variables — if you can draw lines around
groups of subgoals such that some complexity is hidden inside, this suggests a new predicate you
can write and factor the original in terms of it.

Samer

>
> What would be the performance penalty over regular argument binding.
>
> Is such an approach advisable.
>
> All arguments are always ground.
>
> thank you,
>
> Dan
>
> --
> 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.

Dan

unread,
Aug 31, 2018, 11:09:02 AM8/31/18
to SWI-Prolog
Hi Samer, 

Thank you for your suggestion. 

Its good to know about the data flow technique you are describing. 

There is an "architectural" reason for the many parameters. 

At the very center of this part of the system is an automaton, which encodes a fairly complex state machine. Initially, I had the state space represented as a series of predicates -- but this caused two problems:

1. it was slow because there was a lot of backtracking during each "decision cycle" just to identify the right conditions to move the states forward 

2. the condition knowledge became quite distributed across many predicates along with various other predicates that checked for valid preconditions and actions that needed performing -- it was hard to analyze the actual state space -- which in itself needs design during development. 

So, i revamped everything, into a state machine pushing into one spot all variables that have bearing on the state; and ended up with lots of arguments :-)

Dan

Kuniaki Mukai

unread,
Aug 31, 2018, 11:53:49 AM8/31/18
to Dan, SWI-Prolog

On Sep 1, 2018, at 0:09, Dan <gros...@gmail.com> wrote:

Hi Samer, 

Thank you for your suggestion. 

Its good to know about the data flow technique you are describing. 

There is an "architectural" reason for the many parameters. 

At the very center of this part of the system is an automaton, which encodes a fairly complex state machine. Initially, I had the state space represented as a series of predicates -- but this caused two problems:

1. it was slow because there was a lot of backtracking during each "decision cycle" just to identify the right conditions to move the states forward 

2. the condition knowledge became quite distributed across many predicates along with various other predicates that checked for valid preconditions and actions that needed performing -- it was hard to analyze the actual state space -- which in itself needs design during development. 


Reading this, it reminds me of  something like dynamic predicate logic. Roughly  while M l= P  is the traditional model theory of the first order (classical) predicate logic,
but  M  |=  [P] M’ is a way  you are trying to formalize your  problem.   I.e. Interpreting of a formula P changes the current  state M into a next one M’.
I am writing this comment with hope of 1 % relevancy  to  your  problem.   Anyway  I wish your success on the current problem.

Kuniaki Mukai

Peter Ludemann

unread,
Aug 31, 2018, 2:14:47 PM8/31/18
to SWI-Prolog
EDCGs can be used to pass multiple parameters (either input/output "accumulators" or single-value) through a group of predicates (EDCGs use term expansion, so they retain all the indexing performance). The parameters can be accessed by name, either as accumulators (using "[...]:Acc") or as input values or output values (using "X/Acc/Y" notation)

To unsubscribe from this group and stop receiving emails from it, send an email to swi-prolog+unsubscribe@googlegroups.com.

--
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+unsubscribe@googlegroups.com.

Jan Wielemaker

unread,
Sep 1, 2018, 4:03:06 AM9/1/18
to Peter Ludemann, SWI-Prolog
On 31/08/18 20:14, Peter Ludemann wrote:
> EDCGs can be used to pass multiple parameters (either input/output
> "accumulators" or single-value) through a group of predicates (EDCGs use
> term expansion, so they retain all the indexing performance). The
> parameters can be accessed by name, either as accumulators (using
> "[...]:Acc") or as input values or output values (using "X/Acc/Y" notation)
> https://github.com/mndrix/edcg

Yet another solution :) Know there are several aspects: ease and
readability of the program, maintenance and performance. Dicts are
particularly good for passing extensive context such as options of
which the various predicates only use some keys at various isolated
places. In that scenario they are often faster as well due to the
reduced argument passing.

Cheers --- Jan

>
> On 31 August 2018 at 08:53, Kuniaki Mukai <kuniak...@gmail.com
> <mailto:kuniak...@gmail.com>> wrote:
>
>
>
>> On Sep 1, 2018, at 0:09, Dan <gros...@gmail.com
>> <https://groups.google.com/group/swi-prolog>.
>> > For more options, visit https://groups.google.com/d/optout
>> <https://groups.google.com/d/optout>.
>>
>>
>> --
>> 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
>> <mailto:swi-prolog+...@googlegroups.com>.
>> <https://groups.google.com/group/swi-prolog>.
>> For more options, visit https://groups.google.com/d/optout
>> <https://groups.google.com/d/optout>.
>
> --
> 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
> <mailto:swi-prolog+...@googlegroups.com>.
> <https://groups.google.com/group/swi-prolog>.
> For more options, visit https://groups.google.com/d/optout
> <https://groups.google.com/d/optout>.
>
>
> --
> 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
> <mailto:swi-prolog+...@googlegroups.com>.

Dan

unread,
Sep 2, 2018, 12:50:20 AM9/2/18
to SWI-Prolog
Hi Kuniaki,

Yes, indeed, this is a good characterization -- some arguments are indicative of changed "mode" of the system; which across-the-board change (or configure) how certain operations are working. 

Dan

Dan

unread,
Sep 2, 2018, 12:55:05 AM9/2/18
to SWI-Prolog
Hi Jan,

So you are saying that passing 11 arguments vs. say, 1 or 2 arguments and a dict, could *reduce* an argumentation passing overhead.  

What is the overhead of adding another argument to a predicate?

The predicate with the 11 arguments is very time critical, if i can save a bit there, that would add up. 

Dan

Jan Wielemaker

unread,
Sep 2, 2018, 7:38:23 AM9/2/18
to Dan, SWI-Prolog
On 02/09/18 06:55, Dan wrote:
> Hi Jan,
>
> So you are saying that passing 11 arguments vs. say, 1 or 2 arguments
> and a dict, could *reduce* an argumentation passing overhead.
>
> What is the overhead of adding another argument to a predicate?

Do the timing. Most important if you have multiple clauses is indexing.
If that is irrelevant you are in the domain of constant factors. These
are not likely to be very big in this case while you should always
anticipate that SWI-Prolog will in general get smarter and faster, but
not evenly. For larger amounts of code I'd always go for readability and
maintainability. For tiny time critical routines you might try some
alternatives. Just be aware that the tradeof may be different in future
versions. That is not unique: it also happens with CPUs, C/C++ compilers
and just about anything in any language stack.

Cheers --- Jan

> The predicate with the 11 arguments is very time critical, if i can save
> a bit there, that would add up.

A serious option is to write things the way they look good and use
term expansion to translate it into what runs fast.

Paulo Moura

unread,
Sep 3, 2018, 3:43:24 AM9/3/18
to Dan, SWI-Prolog
Hi,

Forget to mention in my suggestion below: you can pass a dict as an object parameter (thus making it available to all predicates), combining both solutions. For example:

:- object(foo(_Dict_)).

:- public(bar/1).
bar(S) :-
S is _Dict_.a1 * _Dict_.a2.

:- end_object.

Sample query:

?- foo(_{a1:2,a2:3})::bar(S).
S = 6.

Cheers,
Paulo

Dan

unread,
Sep 3, 2018, 3:56:51 AM9/3/18
to SWI-Prolog
Hi Paulo,

Thank you.

I tried yesterday for a couple of hours to exchange the long list or arguments with a dict; however, i wasn't able, for some reason (perhaps the eventual late hour), to get it to work -- somehow the passing state -- assocs that are changed -- failed to occur in my code. 

I tried to pass the state within dict (with in_state, out_stage dict "slots") and then outside, along side the dict -- how its essentially done now. 

Having a dict pass arguments had an interesting effect on the code -- some code became much cleaner, while other became, interestingly, harder to read -- as dicts had to be updated -- as state changed during processing. 

Eventually, i went back to my old code -- will probably try again in the future. 


In the meantime i thought to do a  hack and pass the extra parameters I will need via global, backtrackable vars. Its ugly -- but at least its backtrackable :-)

Although, i am thinking that contextual states that stay constant during processing but are required to configure the proessing "mode" could very well be passed via global (backtrackable) vars -- thereby reducing the number of parameters actively passed in the code. 

Dan

Paulo Moura

unread,
Sep 3, 2018, 6:36:36 AM9/3/18
to Dan, SWI-Prolog
Hi Dan,

> On 3 Sep 2018, at 08:56, Dan <gros...@gmail.com> wrote:
>
> Hi Paulo,
>
> Thank you.

You welcome. The full ready to run example is available at:

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

> I tried yesterday for a couple of hours to exchange the long list or arguments with a dict; however, i wasn't able, for some reason (perhaps the eventual late hour), to get it to work -- somehow the passing state -- assocs that are changed -- failed to occur in my code.
>
> I tried to pass the state within dict (with in_state, out_stage dict "slots") and then outside, along side the dict -- how its essentially done now.
>
> Having a dict pass arguments had an interesting effect on the code -- some code became much cleaner, while other became, interestingly, harder to read -- as dicts had to be updated -- as state changed during processing.
>
> Eventually, i went back to my old code -- will probably try again in the future.
>
>
> In the meantime i thought to do a hack and pass the extra parameters I will need via global, backtrackable vars. Its ugly -- but at least its backtrackable :-)

Ugh :-) I still think that for *passing* state to predicates, Logtalk's parametric objects, possibly combined with dicts, provides an elegant and efficient solution. For *threading* state, (E)DCGs is often the best solution.

> Although, i am thinking that contextual states that stay constant during processing but are required to configure the proessing "mode" could very well be passed via global (backtrackable) vars -- thereby reducing the number of parameters actively passed in the code.

Constant values are easy using object parameters. For example:

:- object(circle1(Color),
extends(circle(1, Color))).

:- object(red_circle(Radius),
extends(circle(Radius, red))).

I.e. I simply use derived object that pass the constant values to the generic parametric object.

But trying to do the same with dicts is not working for some reason that I'm trying to understand:

:- object(foo(_Dict_)).

:- public(bar/1).
bar(S) :-
S is _Dict_.a1 + _Dict_.a2.

:- end_object.


:- object(desc(_Dict_),
extends(foo(_Dict_.put(a3,4)))).

:- public(baz/1).
baz(_Dict_).

:- end_object.

The code compiles fine but I get an instantiation error at runtime:

?- desc(_{a1:2,a2:3})::bar(S).
ERROR: Arguments are not sufficiently instantiated

?- D = _{a1:2,a2:3}, desc(D)::bar(S).
ERROR: Arguments are not sufficiently instantiated

Likely due to *when* the functional notation is expanded. Interesting. Debugging...

Cheers,
Paulo

Paulo Moura

unread,
Sep 10, 2018, 6:34:57 AM9/10/18
to Dan, SWI-Prolog
Hi,

> On 3 Sep 2018, at 11:36, Paulo Moura <pjlm...@gmail.com> wrote:
>
> Hi Dan,
>
>> On 3 Sep 2018, at 08:56, Dan <gros...@gmail.com> wrote:
>>
>> Hi Paulo,
>>
>> Thank you.
>
> You welcome. The full ready to run example is available at:
>
> https://github.com/LogtalkDotOrg/logtalk3/tree/master/examples/pardicts
>
>> I tried yesterday for a couple of hours to exchange the long list or arguments with a dict; however, i wasn't able, for some reason (perhaps the eventual late hour), to get it to work -- somehow the passing state -- assocs that are changed -- failed to occur in my code.
>>
>> I tried to pass the state within dict (with in_state, out_stage dict "slots") and then outside, along side the dict -- how its essentially done now.
>>
>> Having a dict pass arguments had an interesting effect on the code -- some code became much cleaner, while other became, interestingly, harder to read -- as dicts had to be updated -- as state changed during processing.
>>
>> Eventually, i went back to my old code -- will probably try again in the future.
>>
>>
>> In the meantime i thought to do a hack and pass the extra parameters I will need via global, backtrackable vars. Its ugly -- but at least its backtrackable :-)
>
> Ugh :-) I still think that for *passing* state to predicates, Logtalk's parametric objects, possibly combined with dicts, provides an elegant and efficient solution. For *threading* state, (E)DCGs is often the best solution.

Btw, I assumed it was clear from my description but still worth stressing that access to Logtalk object parameters is O(1).

>> Although, i am thinking that contextual states that stay constant during processing but are required to configure the proessing "mode" could very well be passed via global (backtrackable) vars -- thereby reducing the number of parameters actively passed in the code.
>
> Constant values are easy using object parameters. For example:
>
> :- object(circle1(Color),
> extends(circle(1, Color))).
>
> :- object(red_circle(Radius),
> extends(circle(Radius, red))).
>
> I.e. I simply use derived object that pass the constant values to the generic parametric object.
>
> But trying to do the same with dicts is not working for some reason that I'm trying to understand:
>
> :- object(foo(_Dict_)).
>
> :- public(bar/1).
> bar(S) :-
> S is _Dict_.a1 + _Dict_.a2.
>
> :- end_object.
>
>
> :- object(desc(_Dict_),
> extends(foo(_Dict_.put(a3,4)))).
>
> :- public(baz/1).
> baz(_Dict_).
>
> :- end_object.
>
> The code compiles fine but I get an instantiation error at runtime:
>
> ?- desc(_{a1:2,a2:3})::bar(S).
> ERROR: Arguments are not sufficiently instantiated
>
> ?- D = _{a1:2,a2:3}, desc(D)::bar(S).
> ERROR: Arguments are not sufficiently instantiated
>
> Likely due to *when* the functional notation is expanded. Interesting. Debugging...

Indeed the functional notation is expanded too soon when using something like:

:- object(desc(_Dict_),
extends(foo(_Dict_.put(a3,4)))).

to simplify handling specialized cases where some parameters are fixed. The instantiation error happens as the predicate lookup (independently of being done at compile time or at runtime) is done with a generalized query, which means that _Dict_ is not instantiated. Could not find an alternative in the documentation on dicts.

Proof Easel

unread,
Sep 10, 2018, 8:25:48 AM9/10/18
to SWI-Prolog
Only if you are inside of a class (I am using common
terminology). But along the class chain, you have
additional overhead:

    O(n) : For making a new term object of the shape
              of the super class.

So if you send a message to a colorpoint term object
with 3 fields (I am using commong terminology),
but this message is handled in the super class,

the colorpoint term object needs to be first unpacked,
and then packed into a point term object. Which takes
O(n) time where n is the size of the involved objects.

A Prolog clause, somewhere in your Logtalk translation:

   p(colorpoint(X,Y,_)) :-
        q(point(X,Y))

Is not for free...

Paulo Moura

unread,
Sep 10, 2018, 11:13:59 AM9/10/18
to SWI-Prolog


> On 10 Sep 2018, at 13:25, Proof Easel <janb...@easel.ch> wrote:
>
> Only if you are inside of a class (I am using common
> terminology). But along the class chain, you have
> additional overhead:
>
> O(n) : For making a new term object of the shape
> of the super class.

That's not how Logtalk works. Objects are not created when a hierarchy of Logtalk parametric objects is used. There is no "making objects of the shape of a class" process here. Do you mean making a new compound term instead?

> So if you send a message to a colorpoint term object
> with 3 fields (I am using commong terminology),

Btw, "term object" is not common terminology. See e.g. the related work session in:

@incollection{pmoura11a,
author = {Paulo Moura},
booktitle = {Applications of Declarative Programming and Knowledge Management},
title = {Programming Patterns for Logtalk Parametric Objects},
editor = "Salvador Abreu and Dietmar Seipel",
series = "Lecture Notes in Artificial Intelligence",
volume = "6547",
publisher = "Springer-Verlag",
address = "Berlin Heidelberg",
pages = "52--69",
month = apr,
year = 2011
}

"parametric theories" in L&O, "parametric objects" in SICStus Prolog Objects library and Logtalk, "parametric units" in GNU Prolog/CX.

> but this message is handled in the super class,

Message in this case implies a public predicate to access the parameter. But local access to parameters don't require defining any predicates. If we have e.g.

:- object(circle(Radius, Color),
extends(ellipse(Radius, Radius, Color))).

the access to the parameters is always local from within any of the objects (the scope of the parameter variables is the object).

> the colorpoint term object needs to be first unpacked,
> and then packed into a point term object. Which takes
> O(n) time where n is the size of the involved objects.

Access to an object parameter from within the object only requires (an implicit) unification at the clause head. The "size of the involved objects" is meaningless. Do you mean the size of the compound terms? There are no objects being created when an object parameter is accessed. If in Dan's code he needs to access e.g. arguments 3, 4, and 7 in predicate foo/0 then he could simply write something like:

foo :- _A3_ * 2 - _A4_ / _A7_.

> A Prolog clause, somewhere in your Logtalk translation:
>
> p(colorpoint(X,Y,_)) :-
> q(point(X,Y))
>
> Is not for free...

Both dynamic and static binding *remove* those inference steps. A simple example (here defining a public predicate to access the object parameter, which is not required for local access to parameters):

---- ellipse.lgt ----
:- object(ellipse(_RX_, _RY_, _Color_)).

:- public(color/1).
color(_Color_).

:- end_object.
---------------------

---- circle.lgt -----
:- object(circle(Radius, Color),
extends(ellipse(Radius, Radius, Color))).

:- end_object.
---------------------

---- circle1.lgt ----
:- object(circle1(Color),
extends(circle(1, Color))).

:- end_object.
---------------------

?- logtalk_load([ellipse, circle, circle1], [optimize(on)]).
% [ /Users/pmoura/Desktop/o1/ellipse.lgt loaded ]
% [ /Users/pmoura/Desktop/o1/circle.lgt loaded ]
% [ /Users/pmoura/Desktop/o1/circle1.lgt loaded ]
% (0 warnings)
true.

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

?- time(ellipse(1,2,red)::color(Color)).
% 21 inferences, 0.000 CPU in 0.000 seconds (80% CPU, 512195 Lips)
Color = red.

?- time(ellipse(1,2,red)::color(Color)).
% 3 inferences, 0.000 CPU in 0.000 seconds (61% CPU, 214286 Lips)
Color = red.

?- time(circle1(red)::color(Color)).
% 27 inferences, 0.000 CPU in 0.000 seconds (83% CPU, 771429 Lips)
Color = red.

?- time(circle1(red)::color(Color)).
% 3 inferences, 0.000 CPU in 0.000 seconds (64% CPU, 166667 Lips)
Color = red.

The predicate lookup for first queries for the ellipse/3 and circle1/1 parametric objects is cached (hence, respectively, the 21 and 27 inference numbers). The second ones show the *same* number of inferences for the bottom object, circle/1, and for the top object, ellipse/1. Thus, even in this case where dynamic binding is used (as we're doing *top-level* queries), the depth of the hierarchy doesn't affect performance (after the first call). We can of course also look into static binding. For example:

---- client.lgt ----
:- object(client).

:- public(top/1).
top(Color) :-
prolog_statistics:time(ellipse(1,2,red)::color(Color)).

:- public(bottom/1).
bottom(Color) :-
prolog_statistics:time(circle1(red)::color(Color)).

:- end_object.
--------------------

Then:

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

?- logtalk_load([ellipse, circle, circle1, client], [optimize(on)]).
% [ /Users/pmoura/Desktop/o1/ellipse.lgt loaded ]
% [ /Users/pmoura/Desktop/o1/circle.lgt loaded ]
% [ /Users/pmoura/Desktop/o1/circle1.lgt loaded ]
% [ /Users/pmoura/Desktop/o1/client.lgt loaded ]
% (0 warnings)
true.

?- client::top(Color).
% 2 inferences, 0.000 CPU in 0.000 seconds (62% CPU, 153846 Lips)
Color = red.

?- client::bottom(Color).
% 2 inferences, 0.000 CPU in 0.000 seconds (63% CPU, 117647 Lips)
Color = red.

In this case, predicate lookup happens at *compile time* (aka static binding). Any "additional overhead along the class chain" is due to defining and calling a (public) predicate to access to parameter and it is not related to the "class chain" by itself.

But my note to Dan was about the time complexity of *access* to object parameters (he mentions 11 arguments in his case). Access time is constant, it doesn't depend on the number or position of the parameters. I.e. O(1).

Dicts value lookup is O(log N). But they are a different construct from object parameters, which are *logical variables*. Dicts provide an useful abstraction that serve a specific purpose with practical use cases. Given that we can always unify a dict with a logical variable, that gives us a nice spectrum of possibilities.

Proof Easel

unread,
Sep 10, 2018, 1:01:41 PM9/10/18
to SWI-Prolog
You still need to make a compound term copy, otherwise
you could not support this/1 and self/2 which deliver
two different compound terms.

Whether you run allong the whole class chain, I do not
know. And whether you do this/1 and self/2 only on
demand, I also do not know.

Without the recreation of a compound term, you
cannot make field access O(1), because you don't
know where the field is,

what you can do is require as a receiver:

    colorpoint(C,point(X,Y))

Then you can use point(X,Y) as the copy via
sharing. But this works only for single inheritance.
And to some extend also for multi-inheritance, if

the multi-inheritance is from disjoint classes. But
if I remember well the colorpoint example
used the following receiver in Logtalk:

   colorpoint(X,Y,C)

Also it was necessary to reimplement the "with_"
method in the subclass, since the superclass has
only the projection compound term available.

Proof Easel

unread,
Sep 10, 2018, 1:06:25 PM9/10/18
to SWI-Prolog
Corr.:
Well the superclass has also the full compound term
available, but it doesn't know anything about it. Like
the field layout, it would need to ask the subclass.

So all the parameters in Logtalks are accessed from
the projected compound term. This means for example
nb_setarg/3 would not work in Logtalk.

You would modify the projected compound term, but
the original compound term, would be left unaffected.
Well I don't want to promote nb_setarg/3,

just saying how Logtalk works...

Proof Easel

unread,
Sep 10, 2018, 1:12:30 PM9/10/18
to SWI-Prolog
I don't know whether SWI-Prolog 7 Prolog dicts are the
better solution. I don't implement them for some special
reasons, except that SWI-Prolog 7 has them.

I still thinks Prolog dicts dont contribute to unification
or indexing, and they also don't really contribute to
field access. But they are populare and advertised.

If you want to have fast field access, you need to forbid
that a subclass decides the compound term layout.
And you need to go as in Java. Java has multiple

inheritance but not for fields. So fields get simply
constant slot numbers. And a field has the same slot
number in the superclass where it was defined and

also in supclass, if more fields are added. Its not
possibly in Java to remove fields down in the subclass
hierarchy. This limitation allows always O(1) field access.

Paulo Moura

unread,
Sep 10, 2018, 1:58:05 PM9/10/18
to SWI-Prolog


> On 10 Sep 2018, at 18:01, Proof Easel <janb...@easel.ch> wrote:
>
> You still need to make a compound term copy, otherwise
> you could not support this/1 and self/2 which deliver
> two different compound terms.

It's self/1. There are no copy_term/2 calls in the implementation of either this/1 or self/1. None is required.

> Whether you run allong the whole class chain, I do not
> know. And whether you do this/1 and self/2 only on
> demand, I also do not know.

Like parameter/1 and parameter variables, calls to this/1 and self/1 are also usually compiled as clause head unifications. For example, if we have an object clause such as:

foo :- this(This), write(This).

In the compiled code, this first and only goal in the body in the resulting clause will be the call to write/1.

> Without the recreation of a compound term, you
> cannot make field access O(1), because you don't
> know where the field is,

Of course we know where the parameters are. *Always*. If you have for example:

:- object(ellipse(_RX_, _RY_, _Color_)).

you know that _Color_ is in the third position. Therefore, when we compile a clause (within the ellipse/3 object) such as:

color(_Color_).

you simply unify the fact argument, _Color_, with the corresponding variable in the implicit execution context argument. I.e. the generated code for this fact is also a Prolog fact.

> what you can do is require as a receiver:
>
> colorpoint(C,point(X,Y))
>
> Then you can use point(X,Y) as the copy via
> sharing. But this works only for single inheritance.

Parametric objects are orthogonal to single vs multiple inheritance. Using multiple inheritance doesn't cause any implementation complications for parametric objects. For example (this is from the "mi" example in the Logtalk distribution):

:- object(xyzt(X, Y, Z, T),
extends((xyz(X, Y, Z), t(T)))).

You could as well define e.g.

:- object(xtzy(X, T, Z, Y),
extends((xyz(X, Y, Z), t(T)))).

We're dealing with logical variables and unification. The order of the parameters is irrelevant. The access is always O(1).

> And to some extend also for multi-inheritance, if
>
> the multi-inheritance is from disjoint classes. But
> if I remember well the colorpoint example
> used the following receiver in Logtalk:
>
> colorpoint(X,Y,C)
>
> Also it was necessary to reimplement the "with_"
> method in the subclass, since the superclass has
> only the projection compound term available.

The reimplementation of the "with_" predicates resulted simply from the need to return a new parametric object identifier (which is simply a Prolog compound term) representing the result of an update. The order of the parameters can be whatever we want. Again, there is no consequence to access time.

> Am Montag, 10. September 2018 17:13:59 UTC+2 schrieb Paulo Moura:
> > On 10 Sep 2018, at 13:25, Proof Easel <janb...@easel.ch> wrote:
> >
> > Only if you are inside of a class (I am using common
> > terminology). But along the class chain, you have
> > additional overhead:
> >
> > O(n) : For making a new term object of the shape
> > of the super class.
>
> That's not how Logtalk works. Objects are not created when a hierarchy of
> Logtalk parametric objects is used. There is no "making objects of the
> shape of a class" process here. Do you mean making a new compound term instead?
>
>

Proof Easel

unread,
Sep 10, 2018, 3:26:21 PM9/10/18
to SWI-Prolog
When I write copy, I dont mean deep copy via copy_term/2.
I mean shallow copy, as it happens inside Prolog clauses,
in every Prolog system. I also used the word pack somewhere.

Here is an example:

    flip((X,Y), (Y,X)).

If you make this query:

    ?- flip((1,2),X).
    X = (2,1)

Then X will point to some sort of shallow copy and
transformation of (1,2). I never said anything about
deep copy, where you also would get new variables.

Prolog systems are classified as copying or sharing.
Even in sharing Prolog systems, a shallow copy
and transformation happens by some magic.

Since Logtalk runs on a hand full of Prolog systems,
you could even measure the cost of this operation
on each system. You will see a lot of differences.

These simple Prolog compounds are all the pain that
consists of building a Prolog system. In a Java Programming
language a shallow copy is much more explicit,

and you would see that something happens on the
heap. In Java we would do such things on the heap.
This is how one would do flip/2 in Java:

       Pair flip(Pair p) {
           return new Pair(p.second, p.first)        /* "copy" */
       }

Thats why I measured the transition from a compound term
to its projection with O(n), for the unpack and pack of
a point compound term, in the following model:

      colorpoint(X,Y,C)

In this model below the transition would be only O(1), no copy
would happen, only unpack:
 
      colorpoint(C,point(X,Y))

But if you have a deep hierarchy, the unpack would be O(m),
where m is the depth of the hierarchy. So m-times unpack
and deref:

     class1(..,     classm(....))

In Java, if you access a field of an object, there was never
any projection anywhere which would have consumed either
O(n1+...+nm) or O(m).

Proof Easel

unread,
Sep 10, 2018, 3:35:48 PM9/10/18
to SWI-Prolog
In my latest module "func", I have now adopted Pythonesk
translation of function on Prolog dicts. If you call a function
on a Prolog dict, there will be no unpack/pack of the Prolog

dict, even if you call a subclass Prolog dict, and if the
function is in a superclass. For example you could define
now in a module "point" this function on Prolog dicts:

    /* module point */
    Pt.dist() := D :-
          D is sqrt(Pt.x**2+Pt.y)

     /* module colorpoint */
     :- reexport(point)

And if you call it for a Prolog dict that is tagged "colorpoint",
and if "colorpoint" reexports "point" as above. Then the call
itself will not involve any copying whatever.

By no copying, I mean no shallow copy/transformation. But
I just notice that my (::)/2 involves some copying (only shallow).
So maybe a future Logtalk could be implemented more

natively, since there is anyway some copying (only shallow)
somewhere? Not sure yet... Thats maybe an interesting idea for
somebody who subscribes to projected compound terms...

Maybe can do my (::)/2 differently in the future...

Proof Easel

unread,
Sep 10, 2018, 3:38:39 PM9/10/18
to SWI-Prolog
BTW: There is also no unpack/pack in the SWI-Prolog translation
of function on dicts. The SWI-Prolog translation uses a call/n,
which anyway pushes arguments on the stack (I guess so),

but a projection of the Prolog dict is not needed. I already
introduced a different argument order than SWI-Prolog, to be
open for future integration with the rest of the object oriented

features of my system...

Proof Easel

unread,
Sep 10, 2018, 3:49:22 PM9/10/18
to SWI-Prolog
BTW: this below is also a shallow copy and transformation:

:- object(desc(_Dict_),
        extends(foo(_Dict_.put(a3,4)))).

Guess what _Dict_.put(a3,4) would do?
  

Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.19)

SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.

    

?- Y = point{x:1,y:2}.put(z,3).

Y = point{x:1, y:2, z:3}. %%% thats a new shallow copy and transformation %%%


https://groups.google.com/d/msg/swi-prolog/V-SJW_syu8E/VNI3x9mNAAAJ

Paulo Moura

unread,
Sep 10, 2018, 5:39:39 PM9/10/18
to SWI-Prolog

> On 10 Sep 2018, at 20:26, Proof Easel <janb...@easel.ch> wrote:
>
> When I write copy, I dont mean deep copy via copy_term/2.
> I mean shallow copy, as it happens inside Prolog clauses,
> in every Prolog system.

So your argument is that, independently of complexity of *any* algorithm implemented in Prolog, internal machinery may introduce overheads due to the way logical inferences are computed? Given that making logical inferences is inherent to using Prolog, the alternative is?

> I also used the word pack somewhere.
>
> Here is an example:
>
> flip((X,Y), (Y,X)).
>
> If you make this query:
>
> ?- flip((1,2),X).
> X = (2,1)
>
> Then X will point to some sort of shallow copy and
> transformation of (1,2). I never said anything about
> deep copy, where you also would get new variables.
>
> Prolog systems are classified as copying or sharing.
> Even in sharing Prolog systems, a shallow copy
> and transformation happens by some magic.
>
> Since Logtalk runs on a hand full of Prolog systems,

Where by hand full you mean most Prolog systems.

> you could even measure the cost of this operation
> on each system. You will see a lot of differences.

Those differences, when they exist, are orthogonal to Logtalk. They will show up in anything that is written in Prolog or, as it's the case currently with Logtalk, that compiles to Prolog. They affect *everything*.

> These simple Prolog compounds are all the pain that
> consists of building a Prolog system. In a Java Programming
> language a shallow copy is much more explicit,
>
> and you would see that something happens on the
> heap. In Java we would do such things on the heap.
> This is how one would do flip/2 in Java:
>
> Pair flip(Pair p) {
> return new Pair(p.second, p.first) /* "copy" */
> }
>
> Thats why I measured the transition from a compound term
> to its projection with O(n), for the unpack and pack of
> a point compound term, in the following model:
>
> colorpoint(X,Y,C)
>
> In this model below the transition would be only O(1), no copy
> would happen, only unpack:
>
> colorpoint(C,point(X,Y))
>
> But if you have a deep hierarchy, the unpack would be O(m),
> where m is the depth of the hierarchy. So m-times unpack
> and deref:
>
> class1(.., classm(....))
>
> In Java, if you access a field of an object, there was never
> any projection anywhere which would have consumed either
> O(n1+...+nm) or O(m).

Already answered that. Even provided examples and benchmarks. For the last time, hierarchy depth is irrelevant. If dynamic binding is used, hierarchy depth only affects the first call, whose generalized form is then cached. If static binding is used, hierarchy depth only affects *compilation times*.

> Am Montag, 10. September 2018 19:58:05 UTC+2 schrieb Paulo Moura:
>
>
> > On 10 Sep 2018, at 18:01, Proof Easel <janb...@easel.ch> wrote:
> >
> > You still need to make a compound term copy, otherwise
> > you could not support this/1 and self/2 which deliver
> > two different compound terms.
>
> It's self/1. There are no copy_term/2 calls in the implementation of either this/1 or self/1. None is required.
>
>

Proof Easel

unread,
Sep 11, 2018, 3:21:04 AM9/11/18
to SWI-Prolog
What about?

:- object(desc(_Dict_),
        extends(foo(_Dict_.put(a3,4)))).

Whats the generalized form of for example _Dict_.put(y,2).
There is no generalized form. Take these varying inputs:

?- D = point{x:X}.put(y,2), D =.. L, nl.

    L = [C'dict', point, 1, X, 2, y].


?- D = point{z:Z}.put(y,2), D =.. L, nl.

    L = [C'dict', point, 2, y, 3, Z].


?- D = point{x:X,z:Z}.put(y,2), D =.. L, nl.

    L = [C'dict', point, 1, X, 2, y, 3, Z].


So you cannot compile Prolog dicts to a generalized form,
even not along a single class subclass relationship,
also not for static calls, where desc/1 is seen, and

where you would know the receiver class.
You might do something, when not dict operation
is involved, but when you compress lets say the

step from the class to the supersuper class with
the method you still have the O(n) effort of unpack/pack
according to the cached generalized form, in each call.

Paulo Moura

unread,
Sep 11, 2018, 5:31:56 AM9/11/18
to SWI-Prolog
Full circle. Already answered in this thread.

Proof Easel

unread,
Sep 11, 2018, 6:29:49 AM9/11/18
to SWI-Prolog

The proof of the pudding is in the eating.

- MIGUEL DE CERVANTES, Don Quixote

- -
Its very important to benchmark OO-systems with examples
that have wide and deep taxonomies. Only this way the
pros and cons of different approaches can be elucidated.

An O(1) access here and then, doesn't mean an overall
performance of this scale. Do you have a large scale
example of some OO-programming with this property?

It would be necessary that it is an example with a lot of
dynamic calls, otherwise it is not enough OO-ish. In case
it doesn't have multiple inheritance of fields, I am pretty

sure it runs much faster on translations that are not of the
Logtalk style. Except if Logtalk would  also detect this
restricted form of field inheritance, and would do something

special for it... Ha Ha, Logtalk could stll improve.

Proof Easel

unread,
Sep 11, 2018, 6:46:56 AM9/11/18
to SWI-Prolog
In general its very difficult to do something special in the
logic programming world, based on logical variables, that
not only avoids copying, but has also ulra fast unfication.

For example if one would use open lists in the methods,
by a translation:

      :- class point.
          ... [X,Y|_] ... :- ...

      :- class colorpoint.
          ... [X,Y,C|_] ... :- ...

Then the copying is gone. But unification is slightly worse.
If lists are cons cells, the unification is less straight. But
well the copying is gone, this is the single inheritance

field case. But Prolog systems don't have open compounds
usually, so you cannot invoke colorpoint(X,Y,C) or
point(X,Y), you might have some copying first to

pass a list. So with Prolog, as it is in the ISO core standard,
we quickly go full circle, since there are not many ways to
provide so called extensible records.

In C an extensible record is very simple. The same address
pointer can point to:

     /* point */
     struct {
         int x;
         int y;
     }

     /* colorpoint */
     struct {
         int x;
         int y;
         int c;
     }

When Niklaus Wirth started his Oberon, he first looked
for extensible records.

     Record types are extensible, that is, a record type
     can be defined as an extension of another record type.
     http://www.ethoberon.ethz.ch/oreport.html

Prolog is among those programming language that has
not something very close to extensible records.

Well its not completely true, Prolog has the open lists,
and anything that is modelled similar to open list, can
do the matching. So maybe a hybrid between some

ideas of SWI-Prolog 7 Prolog dicts, and ordinary
compounds would give a better solution. Like objects
are not compound terms:

      point(X,Y)

      colorpoint(X,Y,C)

But objects would be:

       (point,[X,Y])

       (colorpoint,[X,Y,C])

Thats probably the closes to extensible records in
Prolog by using the ISO core standard only. You can
then directly match in a super class...

Proof Easel

unread,
Sep 12, 2018, 10:42:04 AM9/12/18
to SWI-Prolog
Hi Samer Abdallah,

Here is a SmallTalk like implementation of interpolate.
The implementation doesn't need a base class of
some sort. Since the data objects decide what
they want to do with mul or add.

It is/it is not SmallTalk like in these respects:

1) It doesn't feature a expression evaluator,
    which would show the SmallTalk expression feature.

2) It only features that numbers are also objects,
    and BTW we do the same for the vectors.

So how was it done. Are functions on Prolog dicts involved.
Yes functions on Prolog dicts were involved, we used
a moded version of our module "func", so that we could
treat numbers as objects. The modding reads:

File 1:  func2.pl: We made an alternative is_dict/2 predicate:
=========================================
:- private is_dict2/2.     %%% SmallTalk experiment %%%
is_dict2(D, _) :- var(D), !, fail.
is_dict2(D, floats) :- number(D), !.
is_dict2([_|_], vector) :- !.
is_dict2(D, T) :- !,
is_dict2(D, T).

The implementation is then straight forward. There
is no more base class, and all methods have one less
argument, but this time the "this" is really used.

File 2: floats.pl
================================
:- module(floats, [add/3, mul/3]).
:- use_module(library(advanced/dict)).
:- use_module(library(func2)).

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

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


File 3: vector.pl (we needed to change the argument order of mul)
==================================================
:- module(vector, [mul/3, add/3]).
:- use_module(library(basic/lists)).
:- use_module(library(advanced/dict)).
:- use_module(library(func2)).

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.


File 4: main.pl
===========
:- module(main, [interpolate/4]).
:- use_module(library(advanced/dict)).
:- use_module(library(func2)).

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

To be continued...

Proof Easel

unread,
Sep 12, 2018, 10:49:33 AM9/12/18
to SWI-Prolog
Does it work? Yes it works in Jekejeke Prolog, probably
the same could be done also in SWI-Prolog 7, i.e. mod
the functions on Prolog dicts implementation,

to allow further objects that are not Prolog dicts, but
otherwise useful. Its no hands objects again, just the
ISO module system.

Here are some example runs.

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

Source code and screenshot here:
https://gist.github.com/jburse/0fb17e9a7fe5a4f46a57cb5a109dc312#gistcomment-2704805

Proof Easel

unread,
Sep 12, 2018, 11:05:09 AM9/12/18
to SWI-Prolog
So what is or was SmallTalk? I am pretty sure some
Millenials wont know what is or was Smalltalk.

So here is a little link:

   The Smalltalk Environment
   Larry Tesler, Apple Computer Inc
   Byte Magazin, August 1981
   https://tech-insider.org/star/research/acrobat/8108-a.pdf

It was basically a functional programming AND object oriented
language environment. These are both features which are
somehow initially missing from Prolog. Here is a picture of an

expression that is evaluated with the help of object
oriented message sending (I don't mean the doit,
but the max: message inside the expression):

    

So problem domains can profit more from object oriented
programming when this way of function programming is also
added. Since it allows for more concise formulation.

I guess problem domains that are mathematically inclinde
and that need a lot of long formulas would especially profit,
possibly a Tensor Prolog as well, in the old times they

were happy to control a plotter:

   plotter turn: I8O - (self cornerAngle/2)
   See PDF page physical 14 (logical 118)

Have Fun!

Proof Easel

unread,
Sep 12, 2018, 11:16:40 AM9/12/18
to SWI-Prolog
Corr: I wrote:
1) It doesn't feature a expression evaluator,
    which would show the SmallTalk expression feature.

Well we can also show a little bit of it. Instead of

File 4: main.pl
===========
interpolate(K1, X1, X2, Y) :-
K2 is 1-K1,
KX1 = X1.mul(K1),
KX2 = X2.mul(K2),
Y = KX1.add(KX2).

the following, we can also use the following:

File 5: main2.pl
===========
interpolate(K1, X1, X2, Y) :-
K2 is 1-K1,
    Y = X1.mul(K1).add(X2.mul(K2)).

This should be accepted in SWI-Prolog and Jekejeke Prolog now.
And thanks to the new function expansion in Jekejeke Prolog, it
should be also correctly expanded. Lets make a check:

File 4: main.pl
===========
?- listing.
interpolate(K1, X1, X2, Y) :-
   K2 is 1-K1,
   '.'(X1, mul(K1), A),
   KX1 = A,
   '.'(X2, mul(K2), B),
   KX2 = B,
   '.'(KX1, add(KX2), C),
   Y = C.

File 5: main2.pl
===========
?- listing.
interpolate(K1, X1, X2, Y) :-
   K2 is 1-K1,
   '.'(X1, mul(K1), A),
   '.'(X2, mul(K2), B),
   '.'(A, add(B), C),
   Y = C.

Yes essentially the same. But what would be ultra cool,
if we could just write the following (I am also viewing (is)/2
now SmallTalk like):

interpolate(K1, X1, X2, Y) :-
    Y = X1*K1+X2*(1-K1).

Proof Easel

unread,
Sep 16, 2018, 9:53:04 AM9/16/18
to SWI-Prolog
Ok, I made an experiment with a user module "param",
that provides some new directives, and does everything
on the fly, based on the ISO module standard.

In my language, lets call this language Log-Not-Some-
Nonsense-Talk, I defined point and colorpoint as follows.
The module "param" provides all the necessary rewriting,
so that the below text is accepted.

File point.p:
=======================================
   :- object(point(X,Y)).

   :- public get_x/2.
   get_x(_,X).

   :- public get_y/2.
   get_y(_,Y).

   :- end_object.

File colorpoint.p:
=======================================

:- object(colorpoint(X,Y,_)).

:- extends(point(X,Y)).

:- end_object.

Everything is rewritten to the ISO module standard, and
the usual module/2 and reexport/1 directives are used.
The usual colorpoint/point already works:

    ?- colorpoint(1,2,red)::get_x(X).
    X = 1
    ?- colorpoint(1,2,red)::get_y(Y).
    Y = 2

Provided and missing features so far:

- I do not yet have some way to distinguish static and
  dynamic, so inside the object/1 and end_object/0 parenthesis
  everything is viewed as dynamic. Not yet sure what
  the marker could be, really {}/1 around a clause?

- I do not touch the body or head of a clause. The head
  needs to use Pythonesk call convention extra parameter,
  and the body, in case it would call itself or another module
  would need to use (::)/2.

For more information see:

Preview: Parametric modules via ISO Prolog modules. (Jekejeke)
https://plus.google.com/+JekejekeCh/posts/EK9JGPvivpm

Have Fun!

Proof Easel

unread,
Sep 16, 2018, 9:57:13 AM9/16/18
to SWI-Prolog
Disclaimer: I don't say it has an optimal rewriting. It
serves only some conceptual considerations, a particular
approach with self sending for projections, to dig deeper into
the functional requirements.

The listing/[0,1] predicate is not blocked. Its very
easy to understand what the rewriting does. If one
understands Pythoneks OO and its Prolog variant (::)/2

The (::)/2 operator is based on the ISO module standard
(:)/2. See also my other posts "Best Kept Secret". As
can be seen the rewriting uses module/2 and reexport/1
directive, the later from the ISO module standard.

Rewritten File point:
=======================================
:- module(point, []).

:- public as/3.
as(point(X,Y), point, A) :- !,
   A = point(X,Y).

:- public get_x/2.
get_x(A, X) :-
   A::as(point, point(X,_)).

:- public get_y/2.
get_y(A, Y) :-
   A::as(point, point(_,Y)).

Rewritten File colorpoint:
=========================================
:- module(colorpoint, []).
:- reexport(library(point)).

:- public as/3.
as(colorpoint(X,Y,A), colorpoint, B) :- !,
   B = colorpoint(X,Y,A).
as(colorpoint(X,Y,_), A, B) :-
   point(X,Y)::as(A, B).

In the approach of the modul "param" no rewritten files
are written on  some medium. The rewriting is all done
on the fly through term expansion and the simplification pipeline.

The test here  concerns the question what can we get
out of the ISO Prolog module standard for parametric
modules. Is/was there something in it for parametric modules?
Reply all
Reply to author
Forward
0 new messages