Re: Setting private attributes during object build

2012-02-02 Thread Damian Conway
My thanks to Kris and Moritz for reassuring me that
the Perl 6 initialization semantics remain sane. :-)

In response to yary's original observations, the extra work required to
achieve non-standard semantics doesn't seem unreasonable to me.
Especially as, should one need to do it regularly, one could just create
a role (possibly parametric) to handle all the boilerplate
and then mix just it into any class that requires it.

Damian


Re: Setting private attributes during object build

2012-02-02 Thread yary
On 02/02/2012 07:40 AM, Damian Conway wrote:
> My point was that I don't want the named arguments that BUILD can take
> to be restricted to only the names of public attributes...which was, I
> thought, yary's complaint when writing...

Actually, that *was* one of my complaints, but I was mistaken on that point.

~/rakudo $ perl6
> class A{has $.b; has $!c; submethod BUILD(:$b,:$c,:$x){say "b=$b c=$c x=$x"}}
> A.new(b=>4,c=>5,x=>6)
b=4 c=5 x=6


>If the complaint is that yary wanted to pass positional args to a
>constructor, then I have no problem with having to write one's own
>non-standard new() method to achieve that.

Agreed on that too.

You could break my post down into a few distinct complaints. Moritz
distilled the one that matters the most to me, and I'll quote his
first post in full-

>The current approach is violating the DRY principle. When you write a
>.new method that wants to initialize private attributes, you have to
>repeat all their names again in the signature of your BUILD submethod:
>
>class A {
>   has ($!x, $!y, $!z);
>   method new($x, $y, $z) { self.bless(*, :$x, :$y, :$z) }
>   submethod BUILD(:$!x, :$!y, :$!z) { } # is this repetition really needed?
>}
>
>It also means that private attributes are less convenient to work with
>than those with accessors, which IMHO is a not signal in the right
>direction.

And then Moritz expands on that a bit in a later post.


Damian:
>The whole point of having BUILD() is to separate allocation
> concerns from initialization concerns.

Here's where I am late to the conversation, I hadn't known that
distinction. S12 doesn't talk about the "why" of BUILD/BUILDALL, at
least not that detail. If "BUILD" is for allocation, and "new" is for
initialization, then hiding private attributes from "bless" is forcing
the programmer to use BUILD for initialization which wasn't the
intent.

S12 says this about BUILD: "Whether you write your own BUILD or not,
at the end of the BUILD, any default attribute values are implicitly
copied into any attributes that haven't otherwise been initialized.
Note that the default BUILD will only initialize public attributes;
..."

And that's good, because otherwise your private attributes would not
be totally hidden.

"you must write your own BUILD (as above) in order to present private
attributes as part of your initialization API."

And that's not so good, because it forces BUILD to be used for
initialization, and precludes initializing private attributes anywhere
else, like a "bless" called from the "new" method.

I don't propose having "blessall"/new "bless" set attributes directly,
it will still have to call BUILDALL, so derived classes will still
work properly. If we change "bless" to present private attributes to
BUILDALL, then the filtering out of private attributes would move to
the default "new" instead.

For example, to be clear, what we have now:

> class plain{has $.b; has $!c; method say{say "b=$!b 
> c=$!c"}};plain.new(b=>4,c=>5).say
use of uninitialized value of type Any in string context
b=4 c=

> class cust{has $.b; has $!c; method 
> new(:$b,:$c){self.bless(*,b=>$b,c=>$c)};method say{say "b=$!b c=$!c"}}; 
> cust.new(b=>4,c=>5).say
use of uninitialized value of type Any in string context
b=4 c=

What I'd like to see:

> class plain{has $.b; has $!c; method say{say "b=$!b 
> c=$!c"}};plain.new(b=>4,c=>5).say
use of uninitialized value of type Any in string context
b=4 c=

> class cust{has $.b; has $!c; method 
> new(:$b,:$c){self.bless(*,b=>$b,c=>$c)};method say{say "b=$!b c=$!c"}}; 
> cust.new(b=>4,c=>5).say
b=4 c=5

-y


Re: Setting private attributes during object build

2012-02-02 Thread yary
I think I get this better now.

Currently:
Default "new" passes its capture (named args) to bless. Bless passes
capture (all args) to the default BUILDALL>BUILD. Default BUILD
initializes only public attributes.

My thought:
Default "new" passes only named args matching public attributes to
bless. Bless passes those to the default BUILDALL>BUILD. Default BUILD
initializes both public & private attributes presented to it by
"bless".


Re: Setting private attributes during object build

2012-02-02 Thread Damian Conway
yary wrote:

>>The current approach is violating the DRY principle. When you write a
>>.new method that wants to initialize private attributes, you have to
>>repeat all their names again in the signature of your BUILD submethod:

The other way of looking at this is that redefining the new() is about
changing the constructor interface, whereas defining the BUILD() is
about changing the initialization behaviour.

So the current approach is violating DRY in order to preserve the more
important principle of Separation of Concerns. After all, it doesn't seem
unreasonable that, if you want to modify two behaviours, you have to
rewrite two components to do it.

On the other hand, rather than adding a blessall() alternative,
perhaps we could say that, if the class explicitly redefines new(), then
any call to bless() within that redefined new() will accept both public
and private attributes for auto-initialization...under the theory that,
by redefining new() the class implementor is taking direct
responsibility for the construction...and is willing to live with the
dire consequences if they mess it up.

Incidentally, blessall() seems a dubious name to me, given that we
already have BUILDALL() and CREATEALL(), where the '-ALL' suffix means
something quite different from what the '-all' would mean at the end of
blessall().

Better still, on the principle that abnormal behaviour should always be
explicitly marked and lexically predeclared, perhaps a pragma would
be appropriate:

class A {
has ($!x, $!y, $!z);

method new($x, $y, $z) {
no strict :autoinit;
self.bless(*, :$x, :$y, :$z)
}
}


>>The whole point of having BUILD() is to separate allocation
>> concerns from initialization concerns.
>
> Here's where I am late to the conversation, I hadn't known that
> distinction. S12 doesn't talk about the "why" of BUILD/BUILDALL, at
> least not that detail. If "BUILD" is for allocation, and "new" is for
> initialization, then hiding private attributes from "bless" is forcing
> the programmer to use BUILD for initialization which wasn't the
> intent.

You have it the wrong way round: new() is for allocation; BUILD() is for
initialization. From a design point-of-view, BUILD() probably should
have been called INIT(), but that keyword was already taken.


> "you must write your own BUILD (as above) in order to present private
> attributes as part of your initialization API."
>
> And that's not so good, because it forces BUILD to be used for
> initialization,

...which is precisely what it's supposed to be for. :-)

Damian


Re: Setting private attributes during object build

2012-02-02 Thread Martin D Kealey
> Damian:
> > The whole point of having BUILD() is to separate allocation
> > concerns from initialization concerns.

On Thu, 2 Feb 2012, yary wrote:
> And that's not so good, because it forces BUILD to be used for
> initialization, and precludes initializing private attributes anywhere
> else, like a "bless" called from the "new" method.

Err, isn't that the point: BUILD is for initialization, and bless is for
allocation?

Except that "bless" also does pass-through for to the initializers, which
confuses the mental model. Well, if it's going to do that, perhaps it could
take just one parcel parameter?

(I do find the notion of an inheritable "constructor" a bit odd, upside-down
even. However one of the things I *like* about Perl5 is that a class's
object factory doesn't have to be called "new"; instead it can have a name
that's natural to the domain of the object: File.open, Socket.listen,
Process.run, Timer.start, etc. And object factories can be "instance
methods" too: IO.dup, Socket.accept, String.split, etc.)

The idea of BUILDALL taking care of the hierarchy and each BUILD just
plucking out the named args it needs is quite elegant, but there will
invariably be corner cases where it doesn't fit, particularly if one mixes
in unrelated classes that conflict on their parameter names.

And yet, overriding BUILDALL in an inheritable fashion also seems wrong.

One could revert to the Perl5 and C++ way of doing things: explicitly
calling up the constructor chain. But then how do you make it clear that
BUILDALL is NOT required?

Is there an answer to this conundrum?

-Martin