Re: Setting private attributes during object build
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
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
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
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
> 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