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

Kon*_*ele 7 object raku

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

Bra*_*ert 9

Perl 具有融入语言的哈希特性。因此,要扩展它以使对象表现得像 Hash,您需要告诉运行时做一些不同的事情。

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

因此,您可以创建自己的对象,该对象具有与 Hash 相同的功能,或者甚至只是从它继承。

class Foo is Hash {
}

class Bar does Associative {
  # delegate method calls to a Hash object
  has %!hash handles Hash;
}
Run Code Online (Sandbox Code Playgroud)

这样做的原因does Associative是您可以将其用作支持关联变量的类型。(Hash 已经做了 Associative,所以你也可以继承它。)

my %f is Foo;
my %b is Bar;
Run Code Online (Sandbox Code Playgroud)

要找出您可以编写哪些方法来实现 Hash 索引操作,您可以查看 Hash 实现的方法。由于我们知道自动调用的方法是大写的,我们只需要查看它们。

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)
Run Code Online (Sandbox Code Playgroud)

很明显,以 结尾的方法-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
Run Code Online (Sandbox Code Playgroud)

请注意,上面有一些限制。

在多重赋值中,被调用的方法是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;
Run Code Online (Sandbox Code Playgroud)

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

如果您将一个属性声明为is required唯一的编写方式:

 my %p := Point.new( x => 1, y => 2 );
Run Code Online (Sandbox Code Playgroud)

如果你没有这样做,你可以实现STORE.

class Point does Associative {
  …

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

my %p is Point = x => 1, y => 2;
Run Code Online (Sandbox Code Playgroud)

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

%p = x => 3, y => 4;
Run Code Online (Sandbox Code Playgroud)

这可能不是您想要的。
不过我们可以解决这个问题。只是让它有一个: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
Run Code Online (Sandbox Code Playgroud)

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

my %p is Point = 1,2;
Run Code Online (Sandbox Code Playgroud)

或按名称:

my %p is Point = x => 1, y => 2;
Run Code Online (Sandbox Code Playgroud)

为此,我们可以改变STORE工作方式。我们将只查看列表中的第一个值并检查它是否是关联的。如果是,我们将假设所有参数也是关联的。否则,我们将假设它是一个包含两个值的列表,x并且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;
Run Code Online (Sandbox Code Playgroud)