“Rat”类型的调用者没有这种方法“AngleCosine”
我正在尝试编写一个测试方法来打印以度数给定的角度的余弦值。
我最终想要的是能够解决以下问题
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
回答
问题
45.AngleCosine
-
45是文字。 -
45构造内置类的值Int。 -
没有命名的内置例程
AngleCosine(在Int其他任何地方)。
在标准 Raku 中,形式的语法foo.bar表示“方法调用”。
稍微简化一下,这意味着乐堂应该:
-
假设有一个对应于的例程
AngleCosine。 -
发现与名称相对应的候选例程
AngleCosine(并将它们按顺序排序,最合适的候选者首先列出)。 -
尝试按照适合的顺序绑定到常规候选人,一旦成功绑定,就调用它并认为调用已解决。
-
如果没有候选人成功绑定,则解析尝试失败,抛出“无此类方法”异常。
候选发现步骤由barin的句法形式驱动foo.bar:
-
如果
bar以字母(或下划线)开头,Rakudo 将开始寻找与foo. 在你的榜样,bar是AngleCosine和foo是45,因此搜索将寻找一个名为方法开始AngleCosine在Int类(45是的一个实例Int类)。 -
在搜索与对象关联的方法时,Rakudo 将查看对象的“方法解析顺序”中包含的每个类以发现候选者。在您的示例中,
foois45,因此 MRO 由 列出45.^mro。这显示((Int) (Cool) (Any) (Mu))。
在标准的乐没有命名的方法AngleCosine在任何类的声明Int,Cool,Any,或Mu。因此 Raku 显示错误消息:
No such method 'AngleCosine' for invocant of type 'Int'
解决方案
-
坚持使用精确的语法
45.AngleCosine。这既令人担忧又复杂,将在本答案的最后讨论。 -
使用非方法调用解决方案。例如,请参阅@p6steve 和@ValleLukas 的回答。
-
$x在方法调用的左侧使用适当初始化的变量(例如).而不是值文字(例如45)。看到这种变化的解决方案foo的foo.bar一节。 -
&AngleCosine在方法调用的右侧使用适当更改的例程提及(例如).。看到这种变化的解决方案bar的foo.bar一节。
这改变解决方案foo的foo.bar
您可以更改Angle类的声明,然后实例化并使用这样的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 东西”的解决方案。
另一种选择是使用强制类型。
直到最近,您只能在例行调用中使用强制类型。但是对于上个月左右的 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
这改变解决方案bar的foo.bar
如果您愿意放弃除lhs.rhs语法之外的所有 OO ,您可以编写:
sub AngleCosine ($_) { 'do something with $_' }
say 45.&AngleCosine; # do something with $_
这是有效的,因为前置&命令 Raku 假定它后面的例程:
-
是不是方法,而是一个子程序是要处理,好像它是一个方法,传递的LHS上的值
.作为日常的第一个参数; -
如果不通过搜索发现类;
-
应该,如果第一个字符后的
&是一个字母(或下划线) -这是确实与案件&AngleCosine-通过查找词汇子程序命名空间被发现。
如果你想通过声明一个实际的 method回到面向对象,但仍然把它放在任何类之外,你可以写:
my method AngleCosine { 'do something with $_' }
say 45.&AngleCosine; # do something with $_
第一行,在构造之外声明一个方法class,并使用 amy将方法安装在与非方法子例程完全相同的命名空间中,因此调用语法和效果与上面刚刚解释的示例相同。
如果您想坚持AngleCosine在您的Angle类中使用一个方法,您可以使用上述使用前置的技术&来关闭基于类的自动方法解析,然后直接内联一个块,该块手动分派到您的Angle类中声明的方法:
class Angle {
method AngleCosine { 'do something with self' }
}
say 45.&{ Angle.^find_method('AngleCosine')[0]($_) }
希望这是一个看起来相当可怕的代码,你永远不会想到你必须走这条路,并且原谅我没有进一步解释它。
使用精确句法形式的解决方案 45.AngleCosine
因此,我们最终得出了完全符合您所建议的真正想要的解决方案。
首先,您可以随时通过俚语更改Raku 语法或语义的任何方面。但对于大多数问题,这样做就像使用核电站为灯泡供电。我不打算介绍俚语如何解决这个问题,因为这样做本质上是荒谬的。
剩下最后一种方法:猴子补丁,或者更具体地说是“猴子打字”。希望你能从名字中得到图片;这意味着玩弄类型,通常是一件非常狡猾的事情,如果您不确切知道自己在做什么,甚至可能不知道,这种技术就会把事情搞砸。
乐将让你的猴子打字,尽管它是不可靠的,而不是简单地禁止它。但是为了保护无辜者,Raku 坚持要你绝对清楚地表明你真的想做这种狡猾的事情。因此,您必须使用 LOUD 编译指示(特定的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
此代码augment是Int要添加AngleCosine方法的类。
请注意,您不能将新属性或父类添加到Int. 一旦组成了一个类,对于一个普通的class声明,它在遇到声明}的结尾之后立即发生class,它的属性和父级列表就永久固定了。
所以这种方法不能用于注入你的$.degrees属性,你必须把方法放在 arole而不是 a 中class。(Arole最像 aclass但通常更通用,这是一个示例,说明当role您使用关键字声明一堆方法和/或属性时可以执行某些操作,但如果使用关键字 则不行class。)
这种解决方案不仅无法添加属性或父类,而且由于其他严重问题,它仍然非常不适合大多数用例。从某种意义上说,这甚至比用俚语解决问题还要糟糕,因为我所展示的内容几乎无法为灯泡供电,而且更糟糕的是,它很可能会熔化。
但这比编写俚语要简单得多,至少对于非常简单的情况而言,如果您不关心潜在的崩溃,也许,只是也许,这是正确的事情。特别是,对于那些希望大部分时间都可以工作但偶尔会失败或出于奇怪的原因做奇怪的事情的代码,它是有意义的。你已经说过你的上下文正在测试,如果你的意思是一次性测试,那么也许猴子打字就足够了。
为什么Rat:D类型约束没有做你想要的
这是与您的原始问题相关的“奖励”部分,我将在此处以缩写形式重复:
我正在尝试编写一个测试方法来打印以度数给定的角度的余弦值。
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
我究竟做错了什么?
您可能会合理地问为什么Rat:D在您的AngleCosine方法中显式指定调用者类型没有达到您的预期。
正如在前面使用精确句法形式45.AngleCosine部分的解决方案中所讨论的那样,这被称为“猴子打字”,这是一件很狡猾的事情。因此,尽管乐将让你的猴子与打字,它坚持认为,你让绝对清楚地表明要通过使用一个这样做的MONKEY编译。
然后,您可能仍然会合理地问,为什么 Rakudo 没有产生有用的错误消息来解释它不起作用,以及如何处理它(例如“也许您想要MONKEYpragma?”)。
而这是因为它可以是明确指定的调用者类型有用。因此 Rakudo 不会仅仅因为代码明确指定了调用类型而抱怨代码。例如,如果您不直接与封闭类相矛盾,而只是细化类型,例如成为子类,那么当调用者符合细化时,该方法将起作用,这增加了描述的绑定阶段的细微差别较早的部分。这很有用。所以在这种情况下不需要错误消息。
这给我们留下了一个问题,当明确指定的调用类直接与封闭类相矛盾时,为什么 Rakudo 不显示错误,就像您的情况一样?为什么它不只是猜测您打算修补补丁,并提供有关您可以做什么的指导?
那是因为它不能确定你的封闭类的矛盾是不合适的。有时是。
回答
我正在研究 raku 模块Physics::Unit和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;我把它作为未来增强的问题]
幕后有一些工作正在进行,我使用这些想法(欢迎您从模块源代码查看/复制/改编)...
-
Measure 父类型类,用于组合各种子类型类(如角度)的值和单位
-
使用检测类型和处理单位的多中缀 s(用于 +-*/)覆盖数学运算符 - 还扩展三角函数,如 sin、cos、tan
-
介绍 ?? 表情符号作为绕过“新”结构的一种手段(所以
my $?2 ?? '2.141 radians'是简写my $?2 .= Angle.new(value = 2.141, units => 'radians'); -
提供 .in 方法以从例如转换。弧度到度数(内置的三角函数只处理弧度) - 这里的 fr 示例是来自 Measure.rakumod 的线
multi sin( Angle:D $a ) is export { sin( $a.in('radian').value ); }
回答
self指代调用对象:(参见:raku doc for self)。要调用方法,您必须定义类。有关 raku 中的类方法的更多信息,另请参阅:raku 中的类方法和方法的 raku 文档
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;