在 Raku 中混合私有和公共属性和访问器

p6s*_*eve 13 raku

#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)
Run Code Online (Sandbox Code Playgroud)

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

#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
Run Code Online (Sandbox Code Playgroud)

我喜欢 '=' 赋值的即时性,但我需要多种方法提供的辅助操作的简易性。我明白这是两个不同的世界,它们不会混合。

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

  1. 我觉得 raku 正在指导我不要混合这两种模式——一些属性是私有的,一些属性是公共的,压力是针对方法方法的(有些:冒号中的糖)——这是 Raku 设计的意图吗?
  2. 我错过了什么吗?

Jon*_*ton 14

这是Raku设计的意图吗?

可以公平地说,Raku 在这方面并非完全没有意见。您的问题涉及 Raku 设计中的两个主题,这两个主题都值得讨论。

Raku 具有一流的 l 值

Raku 充分利用了 l 值作为一流的东西。当我们写:

has $.x is rw;
Run Code Online (Sandbox Code Playgroud)

生成的方法是:

method x() is rw { $!x }
Run Code Online (Sandbox Code Playgroud)

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

$obj.x = 42;
Run Code Online (Sandbox Code Playgroud)

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

my $target := $obj.x;
$target = 42;
Run Code Online (Sandbox Code Playgroud)

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

($x, $y) = "foo", "bar";
Run Code Online (Sandbox Code Playgroud)

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

($obj.x, $obj.y) = "foo", "bar";
Run Code Online (Sandbox Code Playgroud)

这一切都是自然而然的。这也是分配给数组和散列切片背后的机制。

还可以使用Proxy来创建一个 l 值容器,其中读取和写入它的行为在您的控制之下。因此,您可以将副作用放入STORE. 然而...

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

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

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

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

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

如果您以更程序化或功能性的风格编写并class用作定义记录类型的手段,那么您会想要哪些东西。

Raku 设计并没有针对在 setter 中做聪明的事情进行优化,因为这被认为是一个不好优化的事情。它超出了记录类型的需要;在某些语言中,我们可能会争辩说我们想要对分配的内容进行验证,但在 Raku 中,我们可以subset为此转向类型。同时,如果我们真的在做 OO 设计,那么我们想要一个隐藏状态模型的有意义行为的 API,而不是从 getter/setter 的角度来思考,这往往会导致无法并置数据和行为,这无论如何都是 OO 的重点。


Eli*_*sen 7

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

嗯,这真的取决于建筑师。但说真的,不,这根本不是 Raku 工作的标准方式。

现在,这将是完全有可能创造一个Attribute在模块的空间,类似的性状is settable,这将创建一个替代存取方法接受一个单一的值来设置该值。在核心中这样做的问题是,我认为世界上基本上有两个阵营关于这样一个mutator的返回值:它会返回值还是值?

如果您有兴趣在模块空间中实现这样的特性,请与我联系。


rai*_*iph 6

我目前怀疑你只是糊涂了。1在我谈到这个之前,让我们从你没有混淆的东西开始:

我喜欢=任务的即时性,但我需要多种方法提供的辅助操作的简易性。...我不明白为什么我不能去$c.v( 43 )设置一个公共属性

可以做所有这些事情。也就是说,您可以同时使用=赋值、多种方法和“just go $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
Run Code Online (Sandbox Code Playgroud)

混淆的可能来源1

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

has $!foo;
method foo () is rw { $!foo }
Run Code Online (Sandbox Code Playgroud)

不过上面说的不太对。鉴于我们看到的行为,编译器的自动生成foo方法以某种方式被声明为任何同名的方法都会默默地隐藏它。2

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

脚注

1请参阅 jnthn 的回答,了解 Raku 关于私有与公共 getter/setter 的观点的清晰、彻底、权威的说明,以及当您声明公共 getter/setter(即 write has $.foo)时它在幕后所做的事情。

2如果声明了一个属性的自动生成的访问器方法only,那么我认为,如果声明了一个具有相同名称的方法,Raku 会抛出异常。如果已声明multi,则在新方法也已声明multi的情况下不应隐藏它,否则应抛出异常。因此,自动生成的访问器被声明为既不是only也不是multi,而是以某种允许静默阴影的方式。