在 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)

到目前为止,非常合理,然后

#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

我喜欢="赋值的即时性,但我需要多种方法提供的辅助操作的简易性.我知道这是两个不同的世界,它们不会混在一起.

I like the immediacy of the ‘=‘ assignment, but I need the ease of bunging in side actions that multi methods provide. I understand that these are two different worlds, and that they do not mix.

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

BUT - I do not understand why I can’t just go $c.v( 43 ) To set a public attribute

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

这是 Raku 的设计意图吗?

is this the intent of Raku's design?

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

It's fair to say that Raku isn't entirely unopinionated in this area. Your question touches on two themes in Raku's design, which are both worth a little discussion.

Raku 充分利用左值作为一流的东西.当我们写:

Raku makes plentiful use of l-values being a first-class thing. When we write:

has $.x is rw;

生成的方法是:

method x() is rw { $!x }

这里的 is rw 表示该方法正在返回一个 l-value - 也就是说,可以分配给的东西.因此,当我们写:

The is rw here indicates that the method is returning an l-value - that is, something that can be assigned to. Thus when we write:

$obj.x = 42;

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

This is not syntactic sugar: it really is a method call, and then the assignment operator being applied to the result of it. This works out, because the method call returns the Scalar container of the attribute, which can then be assigned into. One can use binding to split this into two steps, to see it's not a trivial syntactic transform. For example, this:

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

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

Would be assigning to the object attribute. This same mechanism is behind numerous other features, including list assignment. For example, this:

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

通过构造一个包含容器 $x$yList 来工作,然后赋值运算符在这种情况下对每一边进行迭代做任务.这意味着我们可以在那里使用 rw 对象访问器:

Works by constructing a List containing the containers $x and $y, and then the assignment operator in this case iterates each side pairwise to do the assignment. This means we can use rw object accessors there:

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

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

And it all just naturally works. This is also the mechanism behind assigning to slices of arrays and hashes.

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

One can also use Proxy in order to create an l-value container where the behavior of reading and writing it are under your control. Thus, you could put the side-actions into STORE. However...

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

When we describe OO, terms like "encapsulation" and "data hiding" often come up. The key idea here is that the state model inside the object - that is, the way it chooses to represent the data it needs in order to implement its behaviors (the methods) - is free to evolve, for example to handle new requirements. The more complex the object, the more liberating this becomes.

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

However, getters and setters are methods that have an implicit connection with the state. While we might claim we're achieving data hiding because we're calling a method, not accessing state directly, my experience is that we quickly end up at a place where outside code is making sequences of setter calls to achieve an operation - which is a form of the feature envy anti-pattern. And if we're doing that, it's pretty certain we'll end up with logic outside of the object that does a mix of getter and setter operations to achieve an operation. Really, these operations should have been exposed as methods with a names that describes what is being achieved. This becomes even more important if we're in a concurrent setting; a well-designed object is often fairly easy to protect at the method boundary.

也就是说,class 的许多用途实际上是记录/产品类型:它们的存在只是为了将一堆数据项组合在一起.. 符号不仅生成访问器,而且还:

That said, many uses of class are really record/product types: they exist to simply group together a bunch of data items. It's no accident that the . sigil doesn't just generate an accessor, but also:

  • 选择属性由默认对象初始化逻辑设置(即,class Point { has $.x; has $.y; } 可以实例化为 Point.new(x => 1, y => 2)),并在 .raku 转储方法中呈现.
  • 将属性选择为默认的 .Capture 对象,这意味着我们可以在解构中使用它(例如 sub translate(Point (:$x, :$y)) { ...}).
  • Opts the attribute into being set by the default object initialization logic (that is, a class Point { has $.x; has $.y; } can be instantiated as Point.new(x => 1, y => 2)), and also renders that in the .raku dumping method.
  • Opts the attribute into the default .Capture object, meaning we can use it in destructuring (e.g. sub translated(Point (:$x, :$y)) { ... }).

如果您以更程序化或功能性的风格编写代码并使用 class 作为定义记录类型的方法,您会想要哪些东西.

Which are the things you'd want if you were writing in a more procedural or functional style and using class as a means to define a record type.

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

The Raku design is not optimized for doing clever things in setters, because that is considered a poor thing to optimize for. It's beyond what's needed for a record type; in some languages we could argue we want to do validation of what's being assigned, but in Raku we can turn to subset types for that. At the same time, if we're really doing an OO design, then we want an API of meaningful behaviors that hides the state model, rather than to be thinking in terms of getters/setters, which tend to lead to a failure to colocate data and behavior, which is much of the point of doing OO anyway.