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
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
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
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
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: No, the complaint was that when you write self.bless(*, |%named) in your method 'new', then you have to list each private attribute as submethod BUILD(:$!a, :$!b, ...) { } in order for them to be bound to their values. This is necessary since the change that private attributes cannot be initialized from the default 'new' constructor, because it's 'bless' (or the 'BUILDALL' that bless calls) that distinguishes private and public attributes, not 'new'
Re: Setting private attributes during object build
On 2 February 2012 17:40, Damian Conway wrote: > I sounds like I simply misunderstood yary's problem, but I'd be > very glad to be reassured that's the case. :-) AIUI, yary is talking about the default BUILD submethod that is generated if you don't provide your own (or maybe its the behaviour of BUILDALL when it can't find a BUILD submethod) which sets any public attributes from corresponding named arguments, but doesn't set private atrributes.
Re: Setting private attributes during object build
Moritz clarified: > In BUILD, the object isn't yet fully constructed, and thus using $.attr > (which is really a virtual method call in disguise) is wrong. STD and > niecza already catch that at compile time, and I'm currently trying to > make rakudo catch it too (branch 'has-self' on github). Agreed. That example should certainly use $!attr instead. > More to the point, objects are constructed by .new calling .bless, which > in turn calls BUILDALL which finally calls BUILD. Since .bless only > takes named arguments (besides the candidate to be blessed), how could > BUILDALL know how to call BUILD with positional arguments? Agreed. That example should be: submethod BUILD( :$arg ) {...} > Finally Rakudo, Niecza and Pugs all agree that the default .new and thus > .BUILD only take named arguments, and while that's not authoritative, it > is a strong indicator that the example above contradicts the compiler > writer's understanding of object initialization system. Agreed. I dearly want BUILD to take only named args. 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: >> If one wants to use any argument in the constructor other than a >> public attribute (positional OR named other than an attribute name), >> one must define a "method new( ... )". 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. If anything, we should make it even harder than that. ;-) But if one can't pass arbitrary named values to the standard new() and have one (or more!) BUILD methods correctly use them, like so: class Date { has Num $!seconds_from_epoch; multi submethod BUILD (Str :$date) { $!seconds_from_epoch = _date_from_str($date); } multi submethod BUILD (Int :$year, Int :$month, Int :$day) { $!seconds_from_epoch = _date_from_YMD($year, $month, $day); } # etc. } and then be able to call: my $today= Date.new(date => 'Feb 2, 2012'); my $tomorrow = Date.new(year => 2012, month => 2, day => 3); then we've failed to make Perl 6 even as usable as C++, which is a tragedy. I sounds like I simply misunderstood yary's problem, but I'd be very glad to be reassured that's the case. :-) Damian
Re: Setting private attributes during object build
On 02/02/2012 06:42 AM, Damian Conway wrote: > S12 has an example that explicitly contradicts this constraint that > constructor > args have to be public attribute names: > > submethod BUILD ($arg) { > $.attr = $arg; > } That example is certainly a fossil. In BUILD, the object isn't yet fully constructed, and thus using $.attr (which is really a virtual method call in disguise) is wrong. STD and niecza already catch that at compile time, and I'm currently trying to make rakudo catch it too (branch 'has-self' on github). More to the point, objects are constructed by .new calling .bless, which in turn calls BUILDALL which finally calls BUILD. Since .bless only takes named arguments (besides the candidate to be blessed), how could BUILDALL know how to call BUILD with positional arguments? Finally Rakudo, Niecza and Pugs all agree that the default .new and thus .BUILD only take named arguments, and while that's not authoritative, it is a strong indicator that the example above contradicts the compiler writer's understanding of object initialization system. Cheers, Moritz
Re: Setting private attributes during object build
Yary wrote: > If one wants to use any argument in the constructor other than a > public attribute (positional OR named other than an attribute name), > one must define a "method new( ... )". Huh? I know I've been out of the loop lately, but this seems fundamentally wrong. Constructor args certainly shouldn't be restricted to just the names of public attributes. And one definitely shouldn't have to redeclare new() just to allow for more sophisticated initialization. The whole point of having BUILD() is to separate allocation concerns from initialization concerns. S12 has an example that explicitly contradicts this constraint that constructor args have to be public attribute names: submethod BUILD ($arg) { $.attr = $arg; } Either I'm missing the point here (which is entirely possible ;-) or something isn't right in the current semantics of object initialization. Damian
Re: Setting private attributes during object build
On 02/01/2012 11:41 PM, Carl Mäsak wrote: > Getting back to the topic of the original post: I think "blessall" is > a bad name for what's proposed, and I don't see a fantastically large > need for that functionality. What's wrong with just defining a BUILD > submethod in the class? 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. Cheers, Moritz
Re: Setting private attributes during object build
Looking back at my paltry code, what I ended up doing was having a BUILD submethod that just listed all my attributes, private and public, and an empty block, essentially turning "bless" into "blessall". Which makes submethod BUILD looks like boilerplate, a magic invocation, repeated in my classes. Not very elegant looking. Not horrible, just not as good as it could be IMHO.
Re: Setting private attributes during object build
On Wed, Feb 1, 2012 at 5:41 PM, Carl Mäsak wrote: ... >Getting back to the topic of the original post: I think "blessall" is >a bad name for what's proposed, and I don't see a fantastically large >need for that functionality. What's wrong with just defining a BUILD >submethod in the class? Limiting settable attributes inside "new" feels arbitrary. It frustrated me as a beginner. I don't have an opinion on "blessall" as a concept or as a name; I do like it as a solution to having to put object init code in different blocks. > I also don't see a problem of having to divide initialization code > between .new and .BUILD. They have different purposes -- .new is > outwards-facing, receiving arguments. .BUILD is infrastructural and > inwards-facing, building up (as the name suggests) your attributes. If > you need to modify both these behaviors, you override both. > > // Carl "new" faces outwards but it cannot help but play inwards when it calls "bless". (And if a method "new" does not call "bless", then it isn't a constructor.) -y
Re: Setting private attributes during object build
Jonathan Lang (>>), yary (>): >> Why must we use 'submethod BUILD' instead of 'method BUILD'? >> Is it some sort of chicken-and-egg dilemma? > > from S12: > "Submethods are for declaring infrastructural methods that shouldn't > be inherited by subclasses, such as initializers ... only the methods > are visible to derived classes via inheritance. A submethod is called > only when a method call is dispatched directly to the current class." > > It isn't chicken-and-egg, it's safety. The BUILD submethod doesn't get > used for anything other than the class it is defined in, even if that > class is derived from. When I first used submethods, I thought of them as just subroutine-method hybrids: they are a little like subs because they don't inherit. They are a little like methods because they take an invocant. I didn't much care about the "infrastructural" bit, mostly using them as a kind of private methods. (Nowadays I use private methods as private methods.) :-) Getting back to the topic of the original post: I think "blessall" is a bad name for what's proposed, and I don't see a fantastically large need for that functionality. What's wrong with just defining a BUILD submethod in the class? I also don't see a problem of having to divide initialization code between .new and .BUILD. They have different purposes -- .new is outwards-facing, receiving arguments. .BUILD is infrastructural and inwards-facing, building up (as the name suggests) your attributes. If you need to modify both these behaviors, you override both. // Carl
Re: Setting private attributes during object build
On Wed, Feb 1, 2012 at 3:24 PM, Jonathan Lang wrote: > Why must we use 'submethod BUILD' instead of 'method BUILD'? > Is it some sort of chicken-and-egg dilemma? from S12: "Submethods are for declaring infrastructural methods that shouldn't be inherited by subclasses, such as initializers ... only the methods are visible to derived classes via inheritance. A submethod is called only when a method call is dispatched directly to the current class." It isn't chicken-and-egg, it's safety. The BUILD submethod doesn't get used for anything other than the class it is defined in, even if that class is derived from. -y
Re: Setting private attributes during object build
Jonathan Lang (>): > Why must we use 'submethod BUILD' instead of 'method BUILD'? Is it some > sort of chicken-and-egg dilemma? Philosophically, BUILD submethods are submethods because they do infrastructural stuff that is very tied to the internals of the class. Submethods are "internal" methods rather than "external" methods (like accessors, for example). Submethods don't inherit; each BUILD is specific to the class it finds itself in and not its deriving classes. The only sense in which it's akin to a chicken-and-egg dilemma is that a BUILD submethod assumes that it can access the class's attributes, but not that it can use accessors and other external methods. // Carl
Re: Setting private attributes during object build
Why must we use 'submethod BUILD' instead of 'method BUILD'? Is it some sort of chicken-and-egg dilemma?