Question

I think this is a scope related problem. If I have a rule on my object like this:

:- public(new/2).
:- mode(new(+list, -object_identifier), one).
new(Args, Instance) :-
    self(Self),
    create_object(Instance, [instantiates(Self)], [], []),
    Instance::process_arguments(Args).

I find this works fine if I do this dance:

:- object(name, instantiates(name)).

I don't fully understand why this is necessary but I suspect it is linked to my actual problem, which is that if I have your standard Prolog loop in my object, like so:

process_arguments([Arg|Args]) :- process_arg(Arg), process_arguments(Args).
process_arguments([]).

process_arg(Arg) :- ::asserta(something(Arg)).

I find this use of ::asserta puts the facts in the right namespace (on the newly-created instance). However, if I get witty and replace the body of process_arguments/1 with this lambda expression:

process_arguments(Args) :- meta::map([Arg]>>process_arg(Arg), Args).

then I wind up with my facts being added to the parent class and shared by all the instances. If I replace it with this:

process_arguments(Args) :-
    self(Self),
    meta::map([Arg]>>(Self::process_arg(Arg)), Args).

then it works, but I have to make process_arg/1 a public rule when I'd rather not. What am I missing?

Was it helpful?

Solution

Let me start first with your snippet of code above where the object name instantiates itself. In doing this, you make name its own class. Nothing wrong where. In languages that support meta-classes, such as Smalltalk and Logtalk, making a class its own meta-class is a classical way of avoiding an infinite regression. See, for example the Wikipedia entry on meta-classes (http://en.wikipedia.org/wiki/Metaclass). See also the "reflection" example in the Logtalk distribution. By making the object name instantiate itself, it plays both the role of an instance (as it instantiates an object) and the role of a class (as it's instantiated by an object). If you defined name as a stand-alone object, i.e. an object with no relation to other objects, it would be compiled as a prototype.

Now to your question. In Logtalk, meta-predicates (such as meta::map/2) are called in the context of the sender. If the process_arguments/1 predicate is defined in name, then the execution context (including the value of self) will be name. Thus, the clauses for something/1 will be asserted in name. Your workaround (by using the built-in method self/1) works as expected but it does forces you to declare process_arg/1 public predicate. This is a bug in the stable Logtalk version as it should also work by declaring the process_arg/1 predicate protected or private (as the sender is name and the predicate is declared in the sender). For example:

:- object(name,
    instantiates(name)).

    :- public(new/2).
    :- mode(new(+list, -object_identifier), one).
    new(Args, Instance) :-
        self(Self),
        create_object(Instance, [instantiates(Self)], [set_logtalk_flag(dynamic_declarations, allow)], []),
        meta::map({Instance}/[Arg]>>(Instance::process_arg(Arg)), Args).

    :- private(process_arg/1).
    process_arg(Arg) :-
        ::asserta(something(Arg)).

:- end_object.

I will push the bug fix into the publicly available Logtalk development version later this week. Thanks for driving my attention to this bug.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top