我可以编写一个行为类似于散列的对象吗?

Can I write an object that behaves like a hash?

在 Perl 中有 tie。 Python 支持各种 协议 以便对象可以像字典一样运行。 Raku 有类似的东西吗?
IE。我可以定义一个行为类似于 Hash 的对象吗?那就是:我可以写 $myobject<key> 以结束我自己指定的例程吗?

raku 中,语法 <> 似乎是一个后环路运算符,可以通过 AT-KEYEXISTS-KEY 中描述的多方法重载 https://docs.raku.org/language/subscripts#Methods_to_implement_for_associative_subscripting

Perl 在语言中内置了哈希功能。 因此,要扩展它以使对象的行为类似于哈希,您需要告诉运行时做一些不同的事情。

Raku 并非如此。 Raku 中的哈希只是另一个对象。 哈希索引操作只是另一个可以像重载其他运算符一样重载的运算符。

因此您可以创建自己的对象,该对象具有与哈希相同的功能,甚至可以继承它。

class Foo is Hash {
}

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

does Associative 的原因是您可以将其用作支持关联变量的类型。 (Hash 已经具有关联性,因此您也可以继承它。)

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

要找出您可以编写哪些方法来实现哈希索引操作,您可以查看哈希实现的方法。 因为我们知道自动调用的方法都是大写的,所以我们只需要看一下就可以了。

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)

很明显,以 -KEY 结尾的方法是我们想要编写的方法。 (其他大部分只是对象工件。)

您目前不必编写其中的任何一个来使您的对象类型具有关联性。

如果您不编写特定方法,该功能将无法使用。

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

请注意,以上内容有一些限制。

  • 一次不能分配给多个属性。

      %p< x y > = 1,2;
    
  • 不能在声明中赋值。

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

在多重赋值中,被调用的方法是AT-KEY。因此,要使其正常工作,必须将其标记为 rawrw

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;

这处理了多重赋值,但仍将初始化留在声明中。

如果您将属性声明为 is required,则唯一的写法是:

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

如果您不这样做,您可以实施 STORE

class Point does Associative {
  …

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

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

这也使得您以后也可以分配给它。

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

这可能不是您想要的。
不过我们可以解决这个问题。 只是让它必须有一个 :INITIALIZE 参数。

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

Point 的情况下,我们可能希望能够用两个元素的列表来声明它:

my %p is Point = 1,2;

或按姓名:

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

为此,我们可以更改 STORE 的工作方式。 我们将只查看列表中的第一个值并检查它是否是关联的。 如果是,我们将假设所有参数也是关联的。 否则我们将假设它是一个包含两个值的列表,xy.

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;

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?

简短的回答是。不,核心乐没有。但是有一个模块可以让你轻松做到,只需定义 5 方法来创建作为“真实”哈希的完整功能:Hash::Agnostic

较长的答案是:阅读此问题的其他答案:-)