Going over the Bridge, part 2. Let’s get rid of it

今天,我们继续在 Rakudo Raku 中使用 Bridge 方法。昨天,我们在几个预定义的数据类型中看到了方法的定义。现在是时候看看如何使用该方法。

img

里面有什么?

该方法的主要用途在 Real 角色中,该角色包含以下一组方法:

method sqrt() { self.Bridge.sqrt }
method rand() { self.Bridge.rand }
method sin() { self.Bridge.sin }
method asin() { self.Bridge.asin }
method cos() { self.Bridge.cos }
method acos() { self.Bridge.acos }
method tan() { self.Bridge.tan }
method atan() { self.Bridge.atan }
. . .
method sec() { self.Bridge.sec }
method asec() { self.Bridge.asec }
method cosec() { self.Bridge.cosec }
method acosec() { self.Bridge.acosec }
method cotan() { self.Bridge.cotan }
method acotan() { self.Bridge.acotan }
method sinh() { self.Bridge.sinh }
method asinh() { self.Bridge.asinh }
method cosh() { self.Bridge.cosh }
method acosh() { self.Bridge.acosh }
method tanh() { self.Bridge.tanh }
method atanh() { self.Bridge.atanh }
method sech() { self.Bridge.sech }
method asech() { self.Bridge.asech }
method cosech() { self.Bridge.cosech }
method acosech() { self.Bridge.acosech }
method cotanh() { self.Bridge.cotanh }
method acotanh() { self.Bridge.acotanh }
method floor() { self.Bridge.floor }
method ceiling() { self.Bridge.ceiling }
. . .
multi method log(Real:D: ) { self.Bridge.log }
multi method exp(Real:D: ) { self.Bridge.exp }

有几个例程具有不同的模式,其中该方法被调用两次:一次用于获得所需的功能;其次是强制价值:

multi method atan2(Real $x = 1e0) { self.Bridge.atan2($x.Bridge) }
multi method atan2(Cool $x = 1e0) { self.Bridge.atan2($x.Numeric.Bridge) }
multi method atan2(Real $x = 1e0) { self.Bridge.atan2($x.Bridge) }
multi method atan2(Cool $x = 1e0) { self.Bridge.atan2($x.Numeric.Bridge) }
multi method log(Real:D: Real $base) { self.Bridge.log($base.Bridge) }
. . .
multi sub atan2(Real \a, Real \b = 1e0) { a.Bridge.atan2(b.Bridge) }

正如你所看到的,atan2 函数被定义为方法和子例程。为了更多地混淆你,它有两个版本:

proto sub atan2($, $?) {*}
multi sub atan2(Real \a, Real \b = 1e0) { a.Bridge.atan2(b.Bridge) }
# should really be (Cool, Cool), and then (Cool, Real) and (Real, Cool)
# candidates, but since Int both conforms to Cool and Real, we'd get lots
# of ambiguous dispatches. So just go with (Any, Any) for now.
multi sub atan2( \a, \b = 1e0) { a.Numeric.atan2(b.Numeric) }

最后,一些类型转换的方法:

method Bridge(Real:D:) { self.Num }
method Int(Real:D:) { self.Bridge.Int }
method Num(Real:D:) { self.Bridge.Num }
multi method Str(Real:D:) { self.Bridge.Str }
method Rat(Real:D: Real $epsilon = 1.0e-6) { self.Bridge.Rat($epsilon) }

注意 Real 角色的 Bridge 方法返回一个 Num 值。

一些中缀方法也在使用该方法:

multi sub infix:<+>(Real \a, Real \b) { a.Bridge + b.Bridge }
multi sub infix:<->(Real \a, Real \b) { a.Bridge - b.Bridge }
multi sub infix:<*>(Real \a, Real \b) { a.Bridge * b.Bridge }
multi sub infix:</>(Real \a, Real \b) { a.Bridge / b.Bridge }
multi sub infix:<%>(Real \a, Real \b) { a.Bridge % b.Bridge }
multi sub infix:<**>(Real \a, Real \b) { a.Bridge ** b.Bridge }
multi sub infix:«<=>»(Real \a, Real \b) { a.Bridge <=> b.Bridge }
multi sub infix:<==>(Real \a, Real \b) { a.Bridge == b.Bridge }
multi sub infix:«<»(Real \a, Real \b) { a.Bridge < b.Bridge }
multi sub infix:«<=»(Real \a, Real \b) { a.Bridge <= b.Bridge }
multi sub infix:«≤» (Real \a, Real \b) { a.Bridge ≤ b.Bridge }
multi sub infix:«>»(Real \a, Real \b) { a.Bridge > b.Bridge }
multi sub infix:«>=»(Real \a, Real \b) { a.Bridge >= b.Bridge }
multi sub infix:«≥» (Real \a, Real \b) { a.Bridge ≥ b.Bridge }
multi sub prefix:<->(Real:D \a) { -a.Bridge }

追踪调用

要查看 Bridge 方法何时被调用,让我们做一些简单的实验。我添加了几个 nqp::say 调用并运行 REPL 控制台来调用不同类型变量的 sin 方法。

使用 Num 数据类型时,会调用一个直接方法:

> my Num $n = 1e1; 
10
> $n.sin
Num.sin

这个方法调用底层的 NQP 函数:

proto method sin(|) {*}
multi method sin(Num:D: ) {
    nqp::p6box_n(nqp::sin_n(nqp::unbox_n(self)));
}

使用其他数据类型,您可以通过真实角色出行:

> my Int $i = 1;
1
> $i.sin
Real.sin
Num.sin

> my Rat $r = 1/2;
0.5
> $r.sin
Real.sin
Num.sin

如果它们是从内置类型继承的,则可以使用与自己的类型相同的路径:

> class MyInt is Int {}
> my MyInt $mi = MyInt.new
0
> $mi.sin
Real.sin
Num.sin

摆脱它

我修改了所有使用 Rakudo 克隆方法的地方。大多数情况下,他们被直接调用 Num 方法替代。在一些地方,它会导致像 $x.Num.Num 这样的双重调用,当然这些调用也会减少。

使用更新后的代码,所有来自 Roast 的测试都已通过。作为副作用,在某些情况下,速度增加了大约 3%:

./raku -e'for 1..10_000_000 {Int.new(1).sin}'

这是一个相当广泛的变化,而且还有一件事:当您在新创建的 Real 对象上调用该方法时,会导致无限循环。看起来数字数据类型的错误层次结构是主要原因,但我认为我们至少可以安全地移除 Bridge 方法。

Raku 

comments powered by Disqus