在Raku中混合使用私有属性和公共属性以及访问器


12
#Private attribute example
class C { 
    has $!w;                            #private attribute
    multi method w { $!w }              #getter method
    multi method w ( $_ ) {                 #setter method
        warn “Don’t go changing my w!”;   #some side action
        $!w = $_
    }  
}
my $c = C.new
$c.w( 42 )
say $c.w #prints 42
$c.w: 43
say $c.w #prints 43

#but not
$c.w = 44
Cannot modify an immutable Int (43)

到目前为止,如此合理,然后

#Public attribute example
class C { 
    has $.v is rw    #public attribute with automatic accessors
}
my $c = C.new
$c.v = 42
say $c.v #prints 42

#but not
$c.v( 43 ) #or $c.v: 43
Too many positionals passed; expected 1 argument but got 2

我喜欢'='赋值的即时性,但是我需要轻松捆绑多种方法提供的副作用。我知道这是两个不同的世界,并且它们不会混合在一起。

但是-我不明白为什么我不能只去$ cv(43)设置公共属性

  1. 我觉得raku指导我不要混合使用这两种模式-有些属性是私有的,有些是公共的,压力是方法方法(有些是来自结肠的糖)-这是Raku设计的意图吗?
  2. 我想念什么吗?

您可以使用代理
user0721090601

您如何使用代理?我的意思是,当is rw指定访问器时,它已经返回了一个容器。返回代理不会更改访问器上允许的参数数量。
伊丽莎白·马蒂森

@ElizabethMattijsen也许我误解了这个问题。但是,这似乎工作他想要的东西(同时启用= foo.(foo)设置)和启用的副作用在两种情况下做(但不是唯一时获取):tio.run/...
user0721090601

Answers:


13

这是Raku设计的意图吗?

可以公平地说,Raku在这一领域并没有完全不受质疑。您的问题涉及Raku设计中的两个主题,这两个主题都值得进行一些讨论。

Raku具有一流的l值

Raku大量使用l值作为一流的东西。当我们写:

has $.x is rw;

生成的方法是:

method x() is rw { $!x }

is rw这里表明,该方法返回一个L值 -也就是一些可以分配给。因此,当我们写:

$obj.x = 42;

这不是语法糖:它实际上是一个方法调用,然后将赋值运算符应用于它的结果。这是可行的,因为方法调用返回Scalar属性的容器,然后可以将其分配给该容器。可以使用绑定将其分为两个步骤,以查看它不是琐碎的语法转换。例如,这:

my $target := $obj.x;
$target = 42;

将分配给对象属性。相同的机制是许多其他功能(包括列表分配)的背后。例如,这:

($x, $y) = "foo", "bar";

通过构造一个List包含容器$x和的工作$y,然后在这种情况下,赋值运算符成对地迭代每一边以进行赋值。这意味着我们可以rw在此处使用对象访问器:

($obj.x, $obj.y) = "foo", "bar";

这一切都自然而然地起作用。这也是分配给数组切片和哈希的背后机制。

也可以使用Proxy它来创建一个l值容器,在其中可以对其进行读写操作。因此,您可以将副反应放入STORE。然而...

Raku鼓励使用语义方法而不是“ setter”

当我们描述OO时,经常会出现诸如“封装”和“数据隐藏”之类的术语。这里的关键思想是对象内部的状态模型(即,选择表示其实现其行为所需的数据的方式(方法))可以自由发展,例如处理新需求。对象越复杂,它就会变得越自由。

但是,getter和setter是与状态具有隐式连接的方法。虽然我们可能会声称由于实现方法隐藏而不是因为直接调用方法而不是直接访问状态,但我的经验是,我们很快就结束于外部代码正在执行setter调用序列以实现操作的地方-一种令人羡慕的特征反模式。如果我们正在做的是,它是相当肯定,我们将与不getter和setter混合操作来实现操作的对象的逻辑之外结束。实际上,这些操作应该已经作为带有描述所要实现名称的名称的方法公开。如果我们处于并发设置中,这将变得更加重要。设计良好的对象通常很容易在方法边界进行保护。

就是说,的许多用途class实际上是记录/产品类型:它们的存在是为了简单地将一堆数据项组合在一起。这不是偶然的.印记不只是产生一个访问,也:

  • 禁止该属性由默认的对象初始化逻辑设置(即,class Point { has $.x; has $.y; }可以实例化为Point.new(x => 1, y => 2)),并在.raku转储方法中进行渲染。
  • 将属性设为默认.Capture对象,这意味着我们可以在分解中使用它(例如sub translated(Point (:$x, :$y)) { ... })。

如果要以更具过程性或功能性的风格编写并class用作定义记录类型的方式,则需要哪些东西。

Raku设计并未针对在二传手中做聪明的事情而进行优化,因为这被认为是无法优化的。它超出了记录类型的需要;在某些语言中,我们可能会争辩说我们想对分配的内容进行验证,但是在Raku中,我们可以subset针对该类型进行类型化。同时,如果我们确实在进行OO设计,那么我们想要一个有意义的行为的API,该行为隐藏状态模型,而不是根据吸气剂/吸气剂进行思考,而这往往会导致协同定位失败数据和行为,无论如何,这都是进行面向对象的重点。


关于警告Proxys的一点要点(尽管我建议这样做)。我唯一发现它们非常有用的是我的LanguageTag。在内部,$tag.region返回类型的对象Region(因为它的内部存储),但现实是,它是无限更方便人们说$tag.region = "JP"$tag.region.code = "JP"。在我可以从Str类型中表达强制之前,这实际上只是暂时的,例如,has Region(Str) $.region is rw(需要两个单独的计划中但优先级低的功能)
user0721090601

@Jonathan谢谢您抽出宝贵的时间来阐述设计原理-我怀疑油和水的某些方面,对于像我这样的非CS身体,我真的得到了带有隐藏状态的适当OO与es类的应用之间的区别。一种建立深奥数据持有人的更友好的方法,它将获得C ++博士学位。对于user072 ...对于您对Proxy的想法。我之前确实了解过Proxy,但是怀疑它们(和/或特质)刻意非常繁琐的语法以阻止油和水的混合...
p6steve

p6steve:Raku的真正目的是使最普通/最简单的事情变得超级简单。当您偏离通用模型时,总是有可能的(到目前为止,我认为您已经了解了三种不同的方式来做自己想做的事情……当然还有更多),但这会使您工作一些—足以使确保您正在做的事确实是您想要的。但是通过特质,代理,语等,您可以做到,因此只需要几个额外的字符就可以在需要时真正启用一些很酷的东西。
user0721090601

7

但是-我不明白为什么我不能只去$ cv(43)设置公共属性

好吧,这完全取决于建筑师。但是严重的是,不,那根本不是Raku工作的标准方式。

现在,完全有可能Attribute在模块空间中创建一个特征,例如is settable,这将创建一个替代的访问器方法,该方法接受单个值来设置该值。在核心中执行此操作的问题是,我认为世界上基本上有两个关于此类mutator的返回值的阵营:它将返回值还是值?

如果您有兴趣在模块空间实现这种特征,请与我联系。


1
谢谢@伊丽莎白-这是一个有趣的角度-我只是在这里和那里碰到这个问题,建立一个特质来做这个技巧(或我的技能)没有足够的回报。我真的想尽我所能,并遵循最佳编码实践,并希望Raku的设计能够与最佳实践保持一致,我认为这是最佳实践。
p6steve

6

我目前怀疑您只是感到困惑。1在我谈到这一点之前,让我们从不为之困惑的内容开始:

我喜欢=分配的即时性,但是我需要轻松地将多种方法提供的附带动作捆绑在一起。...我不明白为什么我不能只$c.v( 43 )设置公共属性

可以做所有这些事情。这就是说,如果您想同时使用=分配,多种方法和“随便走$c.v( 43 )”,则可以同时进行以下操作:

class C {
  has $!v;
  multi method v                is rw {                  $!v }
  multi method v ( :$trace! )   is rw { say 'trace';     $!v }
  multi method v ( $new-value )       { say 'new-value'; $!v = $new-value }
}
my $c = C.new;
$c.v = 41;
say $c.v;            # 41
$c.v(:trace) = 42;   # trace
say $c.v;            # 42
$c.v(43);            # new-value
say $c.v;            # 43

可能造成混乱的原因1

在幕后,按照以下方式has $.foo is rw生成属性和单个方法:

has $!foo;
method foo () is rw { $!foo }

上面不是很正确。鉴于我们所看到的行为,编译器的自动生成foo方法以某种方式被声明为,任何同名的方法都将其静默地隐藏起来。2

因此,如果您想要一个或多个与属性同名的自定义方法,则如果希望保留通常负责的行为,则必须手动复制自动生成的方法。

脚注

1请参阅jnthn的答案,以清晰,透彻,权威地说明Raku关于私人vs.公共获取者/设置者的观点,以及在宣布公共获取者/设置者(即写has $.foo)时幕后的作法。

2如果声明了用于属性的自动生成的访问器方法only,那么我认为如果声明了具有相同名称的方法,Raku将引发异常。如果已声明multi,则如果还声明了新方法,则不应对其进行遮蔽,否则multi应抛出异常。因此,自动生成的访问器既不声明only也不声明,multi而是以允许静默阴影的某种方式声明。


啊哈-谢谢@raiph-那就是我觉得不见的东西。现在有道理了。根据Jnthn,我可能会尝试成为一个更好的真正的OO编码器,并保持纯数据容器的设置器风格。但是很高兴知道它在工具箱中!
p6steve
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.