用于实例化参数化角色的对象的重载运算符

Overloading operators for objects that are used to instantiate parameterized roles

在 C++ 中,您可以创建模板化的 classes,它们在 模板化对象和实例化这些对象的 class 必须重载该特定运算符才能使其对象与 模板化 class。例如,BST 实现的 insertion 方法 可能依赖于 < 运算符,因此任何对象都存储在 BST 中 必须实施该运算符。

如果可能的话,我怎样才能对 Raku 中的参数化角色做同样的事情?


为了提供一些上下文,以定义为自己模块的以下参数化角色为例:

role BST[::T] {
    my role BinaryNode[::T] {
        has T $.item           is rw;
        has BinaryNode $.left  is rw;
        has BinaryNode $.right is rw;
    }

    has BinaryNode $!root;

    method insert( BST:D: T $x --> Nil ) {
        self!rec-insert($x, $!root)
    }

    method !rec-insert( T $x, BinaryNode $node is rw --> Nil ) {
        if !$node.defined     { $node = BinaryNode[$(T)].new(item => $x) }
        elsif $x < $node.item { self!rec-insert($x, $node.left) }
        elsif $node.item < $x { self!rec-insert($x, $node.right) }
        else                  { } # Duplicate; do nothing
    }
}

然后,它可以用来存储整数:

use BST;
my $bst = BST[Int].new;

$bst.insert($_) for 6, 3, 2, 1, 4;

但是,我尝试了一些用户定义的类型,但无法使其工作。 假设我们定义了一个 Point2D class 和之间的小于关系 两个 Point2D 个对象由它们到中心的距离定义 (例如,Point2D.new(:3x, :4x) 小于 Point2D.new(:6x, :8y)):

use BST;

class Point2D {
    has $.x;
    has $.y;

    multi method distance {
        (($!x - 0) ** 2 ($!y - 0) ** 2).sqrt
    }
}

multi infix:«<»( Point2D:D $lhs, Point2D:D $rhs --> Bool ) {
    return $lhs.distance < $rhs.distance
}

my $bst = BST[Point2D].new;

$bst.insert(Point2D.new(:1x, :4y));
$bst.insert(Point2D.new(:3x, :4y));

=begin comment
Cannot resolve caller Real(Point:D: ); none of these signatures match:
    (Mu:U \v: *%_)
  in method rec-insert ...
  in method insert ...
=end comment

我没有受过良好教育的猜测是 Point2D 的运算符 < 是词法运算符,因此 BST 没有接受它。 。但是,我认为 BST 没有多大意义,因为特定 class 的对象将以不同方式定义它们的关系。此外,我什至不确定这是否适用于类型捕获。

我不是 100% 确定这是一个长期解决方案,但为什么不制作自定义类型 Cool

class Point2D is Cool {
    has $.x;
    has $.y;

    multi method distance {
        (($!x - 0) ** 2 + ($!y - 0) ** 2).sqrt
    }
    method Numeric() { self.distance }
    method Int() { self.Numeric.Int }
    method Str() { "$!x,$!y" }
}

然后,如果您提供适当的方法,例如 NumericIntStr,您就可以免费获得所有常规比较好东西。

中缀<运算符用于比较实数。

您希望它在数值上比较 .distance 的值。

如果您尝试将对象用作实数,它会自动强制转换为距离,这也许是有道理的。

class Point2D {
    has $.x;
    has $.y;

    method distance {
        (($!x - 0) ** 2 + ($!y - 0) ** 2).sqrt
    }

    method Real { self.distance } # <-----
}

然后内置的 < 会自动执行正确的操作。


我个人会添加一些类型和其他注释。
这也使得 ($!x - 0) 及其等价物 (+$!x) 毫无意义。

class Point2D {
    has Real ( $.x, $.y ) is required;

    method distance (--> Real) {
        sqrt( $!x² + $!y² );
    }

    method Real (--> Real) { self.distance }
}

向 Raku 添加一个功能以进行通用比较可能有意义(cmpbeforeafter

目前那些调用 .Stringy 两个值并比较它们。

您目前可以滥用此方法,方法是使用 .Stringy 方法,该方法 returns 不执行 Stringy 角色的方法。

也许它可以像这样工作:

role Comparable {
    method COMPARE () {…}
}

class Point does Comparable {
    has Real ( $.x, $.y ) is required;

    method distance (--> Real) {
        sqrt( $!x² + $!y² );
    }

    method COMPARE () {
        ($.distance, $!x, $!y)
    }
}

multi sub infix:<cmp> ( Comparable \left, Comparable \right ) {
    nextsame unless left.WHAT =:= right.WHAT;

    return Same if left =:= right;
    return Same if left eqv right;

    left.COMPARE() cmp right.COMPARE()
}

以上将通过 .distance 然后 .x 然后 .y.
进行比较 (当然,在这种情况下,这可能没有多大意义。)