Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning a 'raw' scalar container from AT-POS method (rather than a Proxy instance) in a class that 'does' Positional?

Tags:

raku

I'm attempting to implement a class that 'does' Positional that also allows me to update its values by assigning to the result returned by the AT-POS method. Eventually, I was able to concoct the following class that works as intended:

class Test does Positional
{
    has $.slot_1 is rw = 12;
    has $.slot_2 is rw = 24;

    method AT-POS(\position)
    {
        my $t = self;

        return-rw Proxy.new:

            FETCH => method ()
            {
                position % 2 ?? $t.slot_1 !! $t.slot_2
            },

            STORE => method ($v)
            {
                if position % 2
                {
                    $t.slot_1 = $v
                }
                else
                {
                    $t.slot_2 = $v
                }
            }
    }
}

my $test = Test.new;

die unless $test[2] == 24;

die unless $test[5] == 12;


$test[7] = 120;

die unless $test[2] == 24;

die unless $test[5] == 120;


$test[10] = 240;

die unless $test[2] == 240;

die unless $test[5] == 120;

Would it be possible to somehow (and: simply) return the container bound to $!slot_1 (or $!slot_2) inside the Test class implementation?

Before I discovered the use of Proxy instances I attempted to return (and return-rw) the result of expression position % 2 ?? $!slot_1.VAR !! $!slot_2.VAR, because I'm under the impression that the VAR method gives me access to the underlying container, in the hope that I can simply return it. That didn't really work, and I do not understand why yet: I suspect it somehow gets coerced back to a value somehow?

So in other words: is it possible to simplify my AT-POS implementation in this particular situation?

Thanks,

Regards,

Raymond.

like image 817
chromis Avatar asked Jul 18 '20 12:07

chromis


2 Answers

Assuming you do not want accessors for "slot_1" and "slot_2", and if I understand the question correctly, this would be my implementation. I wouldn't call it a Test class, as that would interfere with the Test class that is used for testing.

class Foo {
    has @elements = 24, 12;

    method AT-POS(Int:D $pos) is raw {
        @elements[$pos % 2]
    }
}

my $f = Foo.new;
say $f[2];  # 24
say $f[5];  # 12

$f[2] = 666;
say $f[4];  # 666

Note that the defaults in the array have changed order, that's to keep the arithmetic in AT-POS simple.

Also note the is raw in the definition of the AT-POS method: it will ensure that no de-containerization will take place when returning a value. This allows you to just assign to whatever $f[2] returns.

Hope this made sense!

Also: the Array::Agnostic module may be of interest for you, to use directly, or to use as a source of inspiration.

like image 193
Elizabeth Mattijsen Avatar answered Sep 18 '22 08:09

Elizabeth Mattijsen


First off if you aren't going to use an attribute outside of the object, there isn't a reason to declare them as public, and especially not rw.

class Foo {
    has $!odd = 12;
    has $!even = 24;

    …
}

You can also directly return a Scalar container from a method. You should declare the method as rw or raw. (raw doesn't guarantee that it is writable.)

class Foo {
    has $!odd = 12;
    has $!even = 24;

    method AT-POS(\position) is rw {
        position % 2 ?? $!odd !! $!even
    }
}

# we actually get the Scalar container given to us
say Foo.new[10].VAR.name; # $!even

Note that even if you declare the attributes as public they still have a private name. The private attribute is always rw even if it isn't publicly declared as rw.

class Foo {
    has $.odd = 12;
    has $.even = 24;

    method AT-POS(\position) is rw {
        position % 2 ?? $!odd !! $!even
    }
}

If you are going to use a Proxy, I would consider moving the common code outside of it.

class Foo {
    has $.odd = 12;
    has $.even = 24;

    method AT-POS(\position) is rw {
        # no need to write this twice
        my $alias := (position % 2 ?? $!odd !! $!even);

        Proxy.new:
            FETCH => method () { $alias },
            STORE => method ($new-value) { $alias = $new-value }
    }
}

Of course the ?? !! code is a core feature of this module, so it would make sense to put it into a single method so that you don't end up with duplicate code all over your class. In this case I made it a private method.

class Foo {
    has $.odd = 12;
    has $.even = 24;

    # has to be either `raw` or `rw`
    # it is debatable of which is better here
    method !attr(\position) is raw {
        position % 2 ?? $!odd !! $!even
    }

    method AT-POS(\position) is rw {
        my $alias := self!attr(position);

        Proxy.new:
            FETCH => -> $ { $alias },
            STORE => -> $, $new-value { $alias = $new-value }
    }
}

Again, not much reason to use a Proxy.

class Foo {
    has $.odd = 12;
    has $.even = 24;

    method !attr(\position) is raw {
        position % 2 ?? $!odd !! $!even
    }

    method AT-POS(\position) is rw {
        self!attr(position);
    }
}

Instead of ?? !! you could use an indexing operation.

method !attr(\position) is raw {
    ($!even,$!odd)[position % 2]
}

Which would allow for a ternary data structure.

method !attr(\position) is raw {
    ($!mod0,$!mod1,$!mod2)[position % 3]
}

There was no need to write the if statement that you did as Raku usually passes Scalar containers around instead of the value.

(position % 2 ?? $t.slot_1 !! $t.slot_2) = $v;
like image 20
Brad Gilbert Avatar answered Sep 17 '22 08:09

Brad Gilbert