Can I write an object that behaves like a hash?

Perl has the Hash feature baked into the language. So to extend it so that an object behaves like a Hash you needed to tell the runtime to do something different.

That is not the case for Raku. A Hash in Raku is just another object. The Hash indexing operation is just another operator that can be overloaded the same way you can overload other operators.

So you can create your own object that has the same features as a Hash, or even just inherit from it.

class Foo is Hash {
}

class Bar does Associative {
  # delegate method calls to a Hash object
  has %!hash handles Hash;
}

The reason to have does Associative is so that you can use it as the type to back an associative variable. (Hash already does Associative so you would inherit that too.)

my %f is Foo;
my %b is Bar;

To find out which methods you can write to implement Hash indexing operations you could look at the methods that Hash implements. Since we know that methods that automatically get called are uppercase, we only need to look at them.

Hash.^methods.map(*.name).grep(/^<:Lu + [-]>+$/)
# (STORE BIND-KEY WHICH AT-KEY ASSIGN-KEY DELETE-KEY
# DUMP BUILDALL ASSIGN-KEY EXISTS-KEY AT-KEY STORE ACCEPTS BUILDALL)

It should be fairly obvious that the methods ending with -KEY are the ones we would want to write. (The other ones are mostly just object artifacts.)

You currently don't have to write any of them to make your object type Associative.

If you don't write a particular method, that feature won't work.

class Point does Associative {
  has Real ($.x, $.y);

  multi method AT-KEY ( 'x' ){ $!x }
  multi method AT-KEY ( 'y' ){ $!y }

  multi method ASSIGN-KEY ( 'x', Real $new-value ){ $!x = $new-value }
  multi method ASSIGN-KEY ( 'y', Real $new-value ){ $!y = $new-value }

  multi method EXISTS-KEY ( 'x' --> True ){}
  multi method EXISTS-KEY ( 'y' --> True ){}
  multi method EXISTS-KEY ( Any --> False ){}
}

my %p is Point;
%p<x> = 1;
%p<y> = 2;

say %p.x; # 1
say %p.y; # 2

Note that above has a few limitations.

  • You can't assign to more than one attribute at a time.

      %p< x y > = 1,2;
    
  • You can't assign the values in the declaration.

      my %p is Point = 1,2;
      my %p is Point = x => 1, y => 2;
    

In the multi-assignment, the method that gets called is AT-KEY. So to make it work those must be marked as raw or rw

class Point does Associative {
  …

  multi method AT-KEY ( 'x' ) is rw { $!x }
  multi method AT-KEY ( 'y' ) is rw { $!y }

  …
}

…

%p<x y> = 1,2;

That takes care of multi assignment, but that still leaves the initialization in the declaration.

If you declared an attribute as is required the only way to write it would be:

 my %p := Point.new( x => 1, y => 2 );

If you didn't do that you could implement STORE.

class Point does Associative {
  …

  method STORE ( \list ) {
    ($!x,$!y) = list.Hash<x y>
  }
}

my %p is Point = x => 1, y => 2;

That also makes it so that you can also assign to it later.

%p = x => 3, y => 4;

Which is possibly not what you wanted.
We can fix that though. Just make it so that there has to be an :INITIALIZE argument.

class Point does Associative {
  …

  method STORE ( \list, :INITIALIZE($) is required ) {
    ($!x,$!y) = list.Hash<x y>
  }
}

my %p is Point = x => 1, y => 2;

# %p = x => 3, y => 4; # ERROR

In the case of Point we might want to be able to declare it wit a list of two elements:

my %p is Point = 1,2;

Or by name:

my %p is Point = x => 1, y => 2;

To do that we can change how STORE works. We'll just look at the first value in the list and check if it is Associative. If it is we will assume all of the arguments are also Associative. Otherwise we will assume that it is a list of two values, x and y.

class Point does Associative {
  …

  method STORE ( \list, :INITIALIZE($) is required ) {
    if list.head ~~ Associative {
      ($!x,$!y) = list.Hash<x y>
    } else {
      ($!x,$!y) = list
    }
  }
}

my %a is Point = x => 1, y => 2;
my %b is Point = 1,2;

In raku the syntactical <> seems to be an postcircumfix operator that can be overloaded via a multi method AT-KEY and EXISTS-KEY as described in https://docs.raku.org/language/subscripts#Methods_to_implement_for_associative_subscripting


Can I define a object that behaves like an hash? That is: if I write $myobject<key> I endup in a function that I can specify myself?

The short answer is. No, there is not in core Raku. But there is a module that makes it easy for you to do, having only to define 5 methods to create full functionality as a "real" Hash: Hash::Agnostic

The longer answer is: read the other answers to this question :-)

Tags:

Object

Raku