Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Re: [Raku] Assigning defaults to attributes that are fixed length lists (and other confusions)

by duelafn (Parson)
on Apr 28, 2021 at 13:01 UTC ( #11131790=note: print w/replies, xml ) Need Help??


in reply to [Raku] Assigning defaults to attributes that are fixed length lists (and other confusions)

If in your first attempt (with unless @!items) you instead use: say "Items: " ~ $hamper.items.perl; you will get

Name: Christmas Basket Items: Array.new(:shape(3,), [Any, Any, Any])

The code has @.items[3] is rw; creates a positional attribute which contains a fixed-length array with 3 elements. Thus, the array is truthy and the unless is not triggered.

When you change to @!items := ('Mince Pie', 'White Wine', 'Stinky Cheese', 'Sardines', 'Dogfood'); you replace the fixed-kength array with a new List. Since @!items is rw that is allowed -- you aren't modifying the fixed length array, you are replacing it. Since the [3] describes the array, not the attribute, this is all fine -- to raku, though not perhaps to you :).

There may be a way to do this using has, but at some point it is reasonable to write your own accessor:

class Hamper { has $.name = 'Christmas Basket'; has Str @!items; method TWEAK(){ self.items = 'Mince Pie', 'White Wine', 'Stinky Cheese' unless + @!items; } method items() is rw { return-rw Proxy.new: FETCH => sub ($) { return @!items }, STORE => sub ($, @items) { die "Wrong length" unless @items.elems == 3; @!items = @items; }; } } my $hamper = Hamper.new; # $hamper.items = 'Mince Pie', 'White Wine', 'Stinky Cheese', 'Dogfood +'; say "Name: " ~ $hamper.name; say "Items: " ~ $hamper.items;

Good Day,
    Dean

  • Comment on Re: [Raku] Assigning defaults to attributes that are fixed length lists (and other confusions)
  • Select or Download Code

Replies are listed 'Best First'.
Re^2: [Raku] Assigning defaults to attributes that are fixed length lists (and other confusions)
by tomgracey (Scribe) on Apr 28, 2021 at 20:00 UTC

    Hi Dean - thanks for that, you really cleared a lot up there.

    "The code has @.items[3] is rw; creates a positional attribute which contains a fixed-length array with 3 elements. Thus, the array is truthy and the unless is not triggered."

    I guess this is just a matter of convention and I am happy for fixed (non-zero) length arrays to be regarded as "true". That would make me right about the error message being slightly misleading, I think?

    Thanks for steering me in the direction of writing my own accessor. I am aware that this is an option - though I haven't experimented with it yet. Actually I was hoping it could be done with has, as I am expecting (too much of?) Raku code to be pretty and boilerplate free. The solution you suggested is a great help - but does seem like quite a lot of code lines. I guess this is just because it's a workaround until the ability to assigning defaults to fixed length arrays gets implemented. Then it will be reduced back to a single line (I suppose).

    Thanks again for your help :)

      Posting a quick update to this as my new and improved understanding of attributes led me to a simple alternative which gave me what I wanted. I realised my main problem was the unless having the wrong effect. The phenomenon of overwriting the fixed length array with a longer one is really just a curiosity, since I don't think there's any practical situation when you'd define an N sized array, and then specify an N + 1 element array as the default. I think the unless issue can be resolved easily in either of 2 ways, depending on what effect is desired:

      class Hamper { has $.name = 'Christmas Basket'; my Str @item_defaults[3] = 'Mince Pie', 'White Wine', 'Stinky Chee +se'; has Str @.items[3] is rw; method TWEAK(){ @!items[ $_ ] ||= @item_defaults[ $_ ] for @item_defaults.keys +; } } my $hamper = Hamper.new; say "Name: " ~ $hamper.name; say "Items: " ~ $hamper.items;

      This simply has code in TWEAK assign defaults on a per element basis. This way you get defaults at the element level, so e.g. if you do this:

      my $hamper = Hamper.new( items => ('Fish', 'Canoe') );

      $hamper.items will end up with the user specified values Fish and Canoe as the first 2 elements, and Stinky Cheese as the 3rd. This might be good for some purposes, but in my case I wanted the defaults to be completely overwritten if the user specified any values for @!items at all. ie in the above example, asking for $hamper.items should return "uninitialized value" for the third item. Only if I don't try to set any items at all should I get the defaults. This seems quite straightforward to implement with grep:

      method TWEAK(){ @!items = @item_defaults unless grep {$_}, @!items; }

      This now achieves everything I hoped:

      • I can insert up to 3 values
      • I get an error trying to insert more
      • I get an error trying to insert a non-Str value
      • I get a set of defaults if I don't specify any at all

      I think if you want exactly 3 (or however many) elements, then writing your own accessor is very likely your best option. (Or perhaps subclassing Array? Not really sure of the details there)

      Anyway, I just thought I'd share in case it helps anyone else out...

        Hi Tom... with you on the SO tone ... here's what I think is going on:

        1. An empty Array is falsey, yet an empty shaped Array is truthy (I would be interested to know why!) - thus, as you have already noted, the original error message is wrong in the suggestion for TWEAK. (You may wish to raise a bug issue at rakudostar on git.) More at https://stackoverflow.com/questions/67373726
        2. You are getting a compiler warning from trying to say ~$hamper.items where there is an empty value (Any) in a shaped Array... this does not happen with a non-shaped Array as the length auto-adjusts to the contents.
          Use of uninitialized value element[2] of type Any in string context. Methods .^name, .raku, .gist, or .say can be used to stringify it to s +omething meaningful. in block <unit> at test.raku line 14
          So, I propose you implement a Str method to bypass (Any) elements and format the object output for .put (or say ~$hamper).
        3. Array elements are each scalar containers and are each rw in any case - has @.items is rw just allows the top level array to be assigned to via a generated setter method. (NB. TWEAK can use the private @!items variable directly to assign the defaults).
        4. You can see this now gives low boilerplate accessors that do what you want (?) and protect from writing beyond 3 elements.
        class Hamper { has $.name = 'Christmas Basket'; has @.items[3]; method TWEAK { @!items = ['Mince Pie', 'White Wine', 'Stinky Cheese'] unless +@!items.any.so; } method Str { "Name: {$!name}\nItems: [{@!items.grep(*.so).join(', ')}]\n"; } } given Hamper.new { .items[2] = 'Hard Cheese'; .put; } #Name: Christmas Basket #Items: [Mince Pie, White Wine, Hard Cheese] given Hamper.new( items => ['Fish', 'Canoe'] ) { .put; .items[2] = 'Bicycle'; .put; #.items[3] = 'Horse'; #fails.. Index 3 for dimension 1 out of +range (must be 0..2) #.items.push: 'Blue Nun'; #fails.. Cannot push a fixed-dimension a +rray } #Name: Christmas Basket #Items: [Fish, Canoe] #Name: Christmas Basket #Items: [Fish, Canoe, Bicycle]

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://11131790]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (7)
As of 2022-12-03 18:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?