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

uzl*_*xxx 5 operator-overloading parametric-polymorphism raku

在 C++ 中,您可以创建在模板化对象上使用特定运算符的模板化类,并且实例化这些对象的类必须重载该特定运算符才能使其对象与模板化类一起使用。例如,insertionBST 实现的方法可能依赖于<运算符,因此任何要存储在 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

$bst.insert($_) for 6, 3, 2, 1, 4;
Run Code Online (Sandbox Code Playgroud)

但是,尝试一些用户定义的类型我一直无法使其工作。假设我们定义了一个Point2D类,并且两个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
Run Code Online (Sandbox Code Playgroud)

我不太受过教育的猜测是<for的运算符Point2D是词法的,因此BST不会接受它。如果在模块中重载运算符,建议将其导出,以便用户useimport模块可以使用它。但是,我认为这没有多大意义,BST因为特定类的对象会以不同的方式定义它们的关系。此外,我什至不确定这是否适用于类型捕获。

Eli*_*sen 5

我不是 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" }
}
Run Code Online (Sandbox Code Playgroud)

然后,如果您提供适当的方法(例如NumericInt和等),您将免费获得所有常规的比较好东西Str


Bra*_*ert 5

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

您希望它以数字方式比较 的值.distance

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

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

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

    method Real { self.distance } # <-----
}
Run Code Online (Sandbox Code Playgroud)

然后内置<自动做正确的事情。


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

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

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

    method Real (--> Real) { self.distance }
}
Run Code Online (Sandbox Code Playgroud)

为 Raku 添加一个特性来进行通用比较 ( cmp, before, after)可能是有意义的

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

您当前可以通过使用.Stringy返回不具有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()
}
Run Code Online (Sandbox Code Playgroud)

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