Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I write an object that behaves like a hash?

Tags:

object

raku

In Perl there is tie. Python supports various protocols so that objects can behave like i.e. a dictionary. Is there something similar in Raku?
I.e. Can I define an object that behaves like a Hash? That is: can I write $myobject<key> to end up in a routine that I can specify myself?

like image 540
Konrad Eisele Avatar asked Jul 23 '20 08:07

Konrad Eisele


People also ask

Is a hash an object?

A hash object is dynamically created in memory at run-time. The size of a hash object grows as items are added and it contracts as items are removed. A hash object consists of key columns, data columns, and methods such as DECLARE, FIND, etc. A hash object's scope is limited to the DATA step in which it is created.

Whats the difference between a hash and an object?

There is no such thing as a hash type in JavaScript. {} is just a short-hand initializer for the Object type. And [] is just a short-hand initializer for the Array type. If you are only looking to store key/value pairs, there is absolutely no need for that 'Hash' type in Prototype.


Video Answer


3 Answers

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;
like image 116
Brad Gilbert Avatar answered Oct 26 '22 17:10

Brad Gilbert


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

like image 38
Konrad Eisele Avatar answered Oct 26 '22 17:10

Konrad Eisele


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 :-)

like image 43
Elizabeth Mattijsen Avatar answered Oct 26 '22 16:10

Elizabeth Mattijsen