'Rat' 类型的调用者没有这样的方法 'AngleCosine'

No such method 'AngleCosine' for invocant of type 'Rat'

我正在尝试编写一个测试方法来打印以筛选以度为单位的角度的余弦值。

我最终想要的是能够解决以下问题

say 45.AngleCosine;

没有 .new 东西,比如

say .7.cos;

如何放弃 .new 内容?

这是我目前拥有的:

   class Angle {
       has Numeric $.degrees;

       method AngleCosine( $debug = False )  {
          my Str     $SubName = &?ROUTINE.name;
          my Numeric $radians = self.degrees * π / 180;
          my Numeric $cosine  = $radians.cos;

          if $debug  {
             print "$SubName debugging:\n";
             print "   self    = <" ~ self.degrees ~ ">\n";
             print "   radians = <$radians>\n";
             print "   Cosine of " ~ self.degrees ~  " degrees is <" ~ $cosine ~ ">\n";
          }
          return $cosine;
       }
    }

my $x = Angle.new( degrees=> 45 );
say $x.AngleCosine;
0.7071067811865476

say $x.AngleCosine( True );
AngleCosine debugging:
 self    = <45>
 radians = <0.7853981633974483>
 Cosine of 45 degrees is <0.7071067811865476>
0.7071067811865476

print Angle.new( degrees => 45 ).AngleCosine( True ) ~ "\n";
AngleCosine debugging:
   self    = <45>
   radians = <0.7853981633974483>
   Cosine of 45 degrees is <0.7071067811865476>
0.7071067811865476

self引用调用对象:(参见:raku doc for self). To call a method, you have to define the class. For more info about class method in raku see also: class method in raku and raku doc for method

class Angle {
    method AngleCosine( Rat:D $x --> Rat:D )  {
        my Numeric $radians = $x*π/180;
        say "self   = <$x>";
        say "radians = <$radians>";
        $radians.cos.Rat;
    }
}

say Angle.AngleCosine: 45.0;

问题

45.AngleCosine
  • 45 是一个 literal.

  • 45构造一个内置值classInt.

  • 没有名为 AngleCosine 的内置例程(在 Int 或其他任何地方)。


在标准 Raku 中,foo.bar 形式的语法表示 "method call".

稍微简化一下,这意味着 Rakudo 应该:

  • 假设有一个套路对应AngleCosine.

  • 找出名字AngleCosine对应的candidate个routines(并排序,适者在前) .

  • 尝试binding按照候选人的适合度排序,一旦成功绑定,调用它并认为调用已解决.

  • 如果没有候选人成功绑定,则解析尝试失败,抛出“没有这样的方法”异常。


候选发现步骤由 foo.barbar 的句法形式驱动:

  • 如果 bar 以字母(或下划线)开头,Rakudo 将开始寻找与 foo 关联的方法。在您的示例中,barAngleCosinefoo45,因此搜索将从在 Int 中查找名为 AngleCosine 的方法开始class(45Int class 的实例)。

  • 在搜索与对象关联的方法时,Rakudo 将查看对象的“方法解析顺序”中包含的每个 classes 以发现候选方法。在您的示例中,foo45,因此 MRO 由 45.^mro 列出。这显示 ((Int) (Cool) (Any) (Mu)).


在标准 Raku 中,没有在任何 class 中声明名为 AngleCosine 的方法 IntCoolAnyMu。因此 Raku 显示错误消息:

No such method 'AngleCosine' for invocant of type 'Int'

解决方案

  • 坚持使用精确的语法 45.AngleCosine。这是令人担忧和复杂的,将在本答案的最后讨论。

  • 使用非方法调用解决方案。例如,请参阅@p6steve 和@ValleLukas 的回答。

  • 在方法调用. 的左侧使用适当初始化的变量(例如$x)而不是值文字(例如45)。请参阅下面的 更改 foo.barfoo 部分的解决方案。

  • 在方法调用 . 的右侧使用适当更改的例程提及(例如 &AngleCosine)。请参阅下面的 更改 foo.bar 部分的 bar 的解决方案。

改变 foo.barfoo 的解决方案

您可以更改 Angle class 的声明,然后实例化并使用 Angle 实例,如下所示:

class Angle {
  has Numeric $.degrees;
  method AngleCosine { 'do something with $!degrees' }
}

my Angle $x .= new: degrees => 45;

say $x.AngleCosine; # 'do something with $!degrees'

但是您说过您想要一个“没有 .new 东西”的解决方案。

另一种选择是使用 coercion type

直到最近,您只能在例程调用中使用强制类型。但是对于上个月左右的 Rakudo 版本,类似以下的内容应该可以工作:

class Angle {
  has Numeric $.degrees;
  method COERCE ( Numeric $degrees ) { self.new: :$degrees }
  method AngleCosine { 'do something with $!degrees' }
}

my Angle() $x = 45;

say $x.AngleCosine; # do something with $!degrees

改变 foo.barbar 的解决方案

如果您愿意放弃除 lhs.rhs 语法之外的所有 OO,您可以这样写:

sub AngleCosine ($_) { 'do something with $_' }

say 45.&AngleCosine; # do something with $_

这是有效的,因为前缀 & 指示 Raku 假定其后的例程:

  • 不是一个方法,而是一个子程序就好像它是一个方法,将 . 的 LHS 值作为例程的第一个参数传递;

  • 应该通过搜索classes;

    被发现
  • 应该,如果第一个字符之后&是一个字母(或下划线)——[=确实是这种情况54=] -- 通过查看词法子程序命名空间来发现。


如果您想通过声明一个 实际方法 回到面向对象的方向,但仍将其保留在任何 class 的 之外 ], 你可以这样写:

my method AngleCosine { 'do something with $_' }

say 45.&AngleCosine; # do something with $_

第一行,在 class 构造的 外部 声明一个方法,并使用 my,将该方法安装在与非方法子程序,所以调用语法和效果与上面刚刚解释的例子相同。


如果您想在 Angle class 中坚持使用 AngleCosine 方法,您可以使用上述使用前置 & 的技术来关闭基于自动 class 的方法解析,然后直接内联 手动分派到 Angle class:[=107 中声明的方法=]

class Angle {
  method AngleCosine { 'do something with self' }
}

say 45.&{ Angle.^find_method('AngleCosine')[0]($_) }

希望这是一个看起来很可怕的代码,你永远不会想甚至认为你必须走这条路,请原谅我没有进一步解释它。

使用精确句法形式的解决方案45.AngleCosine

因此,我们最终得出了完全符合您所建议的您真正想要的解决方案。

首先,您可以始终通过俚语改变任何 Raku 语法或语义的方面。但对于大多数问题,这样做就像用核电站给灯泡供电一样。我不打算讨论俚语如何解决这个问题,因为这样做本质上是荒谬的。

剩下最后一种方法:monkey patching,或者更具体地说是“猴子打字”。希望你从名字中得到了图片;这意味着在类型上胡闹,并且通常是一件 非常 狡猾的事情,如果您不确切知道自己在做什么,这种技术会把事情搞砸,甚至可能那么

Raku 让你胡闹打字,尽管它很狡猾,而不是简单地禁止它。但为了保护无辜者,Raku 坚持让你绝对清楚你真的想做这种狡猾的事情。所以你必须使用 LOUD pragma(特定的 use MONKEY-TYPING; 或一般的 use MONKEY;)。

去掉前言,下面是代码:

role Angle { method AngleCosine { 'type has been monkey patched' } }
use MONKEY-TYPING;
augment class Int does Angle {}

say 45.AngleCosine; # type has been monkey patched

此代码 augmentInt class 中添加一个 AngleCosine 方法。

请注意,您不能Int添加新的属性或父classes。一旦组成了 class,对于普通的 class 声明,在遇到 class 声明末尾的 } 之后,它的属性列表和parents 是永久固定的。

所以这种方法不能用于注入你的 $.degrees 属性,你必须将方法放在 role 而不是 class 中。 (roleclass 很像,但通常更通用,这是一个示例,说明如果使用关键字 and/or 声明一堆方法属性,则何时可以执行某些操作=86=],但如果使用关键字 class 则不会。)


此解决方案不仅无法添加属性或父级 class,而且由于 其他 严重问题,它仍然非常不适合大多数用例。从某种意义上说,这比用俚语解决你的问题还要糟糕,因为我所展示的东西几乎无法为灯泡供电,而且更糟糕的是,它可能会熔化。

但这比写俚语要简单得多,至少对于非常简单的情况是这样,如果你不关心潜在的崩溃,也许,只是也许,这是正确的事情。特别是,对于希望大部分时间都能正常工作但允许偶尔失败或出于奇怪原因做奇怪事情的代码来说,它是有意义的。您已经说过您的上下文正在测试,如果您的意思是一次性测试,那么也许猴子打字就足够了。

为什么 Rat:D 类型约束没有达到您的要求

这是与您的原始问题相关的“奖励”部分,我将在此处以缩写形式重复:

I am trying to write a test method to print to screen the cosine of an angle given in degrees.

class Angle {
    method AngleCosine( Rat:D: --> Rat:D ) { ... }
}

say (45.0).AngleCosine

No such method 'AngleCosine' for invocant of type 'Rat'
  in block <unit> at <unknown file> line 1 

What am I doing wrong?

您可能会问,为什么在您的 AngleCosine 方法中明确指定调用者类型 Rat:D 没有达到您的预期。

正如前面 使用精确句法形式的解决方案 45.AngleCosine 部分所讨论的,这被称为“猴子打字”,这是一种狡猾的做法。因此,虽然 Raku 让您随意输入,但它坚持要求您通过使用 MONKEY 编译指示来明确表示您想要这样做。

然后您可能仍然会合理地问为什么 Rakudo 没有产生有用的错误消息来解释它不起作用,以及如何处理它(例如“也许您想要 MONKEY pragma?” ).

那个是因为它可以对显式指定调用者类型有用。所以 Rakudo 不会仅仅因为它明确指定了一个调用类型就抱怨代码。例如,如果您不 直接矛盾 封闭的 class,而只是细化类型,例如成为一个子 class,那么该方法将当 invocant 符合细化时工作,这为前面部分中描述的活页夹阶段添加了细微差别。这很有用。所以在那种情况下不需要错误消息。

这给我们留下了一个问题,当明确指定的调用者 class 直接与 封闭的 class 相矛盾时,为什么 Rakudo 不显示错误,因为你的情况是这样吗?为什么它不只是猜测您的意思是猴子补丁,并提供有关您可以做什么的指导?

那是因为它不能肯定知道你的封闭class的矛盾是不合适的。有时是。

我正在开发 raku 模块 Physics::Unit and Physics::Measure,我最近添加了 Physics::Measure::Angle 类型。

正如您在 Physics::Measure、

的概要中看到的
#Angles use degrees/minutes/seconds or decimal radians
    my $θ1 ♎️ <45°30′30″>;      #45°30′30″ 
    my $θ2 ♎️ '2.141 radians';  #'2.141 radian'

# Trigonometric functions sin, cos and tan (and arc-x) handle Angles
    my $sine = sin( $θ1 );      #0.7133523847299412
    my $arcsin = asin( $sine, units => '°' ); #45°30′30″
#NB. Provide the units => '°' tag to tell asin you want degrees back

[不可否认,这目前仅作为子调用实现,不是您想要的方法格式say $θ1.sin;我已将其作为 issue 用于将来的增强]

幕后有一些工作正在进行,我使用这些想法(欢迎您查看/复制/改编自模块源代码)...

  • 测量父类型 class 用于组合各种子类型 class 的值和单位,例如角度

  • 使用检测类型和处理单位的多中缀 s(对于 +-*/)覆盖数学运算符 - 还扩展三角函数,例如 sin、cos、tan

  • 引入 ♎️ 表情符号作为回避 'new' 结构的一种方式(所以 my $θ2 ♎️ '2.141 radians' 是 shorthand for my $θ2 .= Angle.new(value = 2.141, units => 'radians');

  • 提供 .in 方法从例如转换。弧度到度数(内置三角函数仅处理弧度)- 这里的示例是来自 Measure.rakumod

    的行
    multi sin( Angle:D $a ) is export {
        sin( $a.in('radian').value );
    }