在 Raku 中混合私有和 Public 属性和访问器
Mixing Private and Public Attributes and Accessors in 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
我喜欢“=”赋值的即时性,但我需要在多方法提供的辅助操作中轻松进行。我明白这是两个不同的世界,不能混为一谈。
但是 - 我不明白为什么我不能去
$c.v( 43 )
设置 public 属性
- 我觉得 raku 引导我不要混合这两种模式 - 一些属性是私有的,一些是 public 并且压力是针对方法方法的(有一些:来自冒号的糖) - 是这个Raku的设计意图?
- 我是不是漏掉了什么?
BUT - I do not understand why I can’t just go $c.v( 43 ) To set a public attribute
好吧,这完全取决于架构师。但说真的,不,这根本不是 Raku 的标准工作方式。
现在,完全有可能在模块 space 中创建一个 Attribute
特征,类似于 is settable
,这将创建一个替代访问器方法 would 接受单个值来设置值。这样做的核心问题是,我认为世界上基本上有 2 个阵营关于这样一个突变器的 return 值: return new 值,或 旧 值?
如果您有兴趣在模块 space 中实现这样的特性,请与我联系。
我目前怀疑你只是感到困惑。1 在我谈及之前,让我们从你不感到困惑的地方重新开始:
I like the immediacy of the =
assignment, but I need the ease of bunging in side actions that multi methods provide. ... I do not understand why I can’t just go $c.v( 43 )
To set a public attribute
你可以做所有这些事情。也就是说,如果你想同时使用 =
赋值、多种方法和 "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
一个可能的混淆来源1
在幕后,has $.foo is rw
生成一个属性和一个方法,如下所示:
has $!foo;
method foo () is rw { $!foo }
不过上面说的不太对。考虑到我们看到的行为,编译器自动生成的 foo
方法以某种方式声明,任何同名的 new 方法都会默默地 shadows 它.2
因此,如果您想要一个或多个与属性同名的自定义方法,您必须手动复制自动生成的方法,如果您希望保留通常的行为负责.
脚注
1 请参阅 jnthn 的回答,以清楚、透彻、权威地解释 Raku 关于私人与 public getters/setters 的观点以及它在幕后的作用当您声明 public getters/setters(即写 has $.foo
)时。
2 如果为属性声明了一个自动生成的访问器方法 only
,那么 Raku 会,我推测,如果一个具有相同名称的方法是宣布。如果它被声明为 multi
,那么如果新方法也被声明为 multi
,则不应隐藏它,否则应抛出异常。因此,自动生成的访问器的声明既没有 only
也没有 multi
而是以某种允许静默阴影的方式。
is this the intent of Raku's design?
可以说 Raku 在这方面并非完全没有主见。你的问题涉及Raku设计中的两个主题,都值得讨论一下。
Raku 有第一-class 个左值
Raku 大量使用左值作为第一个 class 事情。当我们写:
has $.x is rw;
生成的方法是:
method x() is rw { $!x }
这里的 is rw
表示该方法正在返回一个 l-value - 即可以分配给的东西。因此当我们写:
$obj.x = 42;
这不是语法糖:它实际上是一个方法调用,然后将赋值运算符应用于它的结果。这行得通,因为方法调用 returns 属性的 Scalar
容器,然后可以将其赋值进去。可以使用绑定将其分成两步,看看它不是一个微不足道的句法转换。例如,这个:
my $target := $obj.x;
$target = 42;
将分配给对象属性。许多其他功能都采用相同的机制,包括列表分配。例如,这个:
($x, $y) = "foo", "bar";
通过构造一个包含容器 $x
和 $y
的 List
来工作,然后赋值运算符在这种情况下对每一侧进行迭代以进行赋值。这意味着我们可以在那里使用 rw
对象访问器:
($obj.x, $obj.y) = "foo", "bar";
这一切都自然而然地起作用了。这也是分配给数组和散列的切片背后的机制。
也可以使用 Proxy
来创建一个左值容器,读写行为在您的控制之下。因此,您可以将副动作放入 STORE
。然而...
Raku 比 "setters"
更鼓励语义方法
当我们描述 OO 时,经常会出现 "encapsulation" 和 "data hiding" 这样的术语。这里的关键思想是对象内部的状态模型——即它选择表示实现其行为(方法)所需数据的方式——可以自由发展,例如处理新需求。对象越复杂,就越自由。
然而,getters 和 setters 是与状态具有隐式连接的方法。虽然我们可能声称我们正在实现数据隐藏,因为我们正在调用一个方法,而不是直接访问状态,但我的经验是,我们很快就会到达一个地方,外部代码正在进行 setter 调用序列以实现操作 - 这是特征羡慕反模式的一种形式。如果我们正在做 that,可以肯定的是我们最终会在对象之外的逻辑中混合执行 getter 和 setter 操作实现一个操作。实际上,这些操作应该作为方法公开,其名称能够描述正在实现的目标。如果我们处于并发环境中,这一点就变得更加重要;设计良好的对象通常很容易在方法边界处得到保护。
也就是说,class
的许多用途实际上是 record/product 类型:它们的存在只是为了将一堆数据项简单地组合在一起。 .
印记不仅生成访问器,而且还生成:
并非偶然
- 选择由默认对象初始化逻辑设置的属性(即
class 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 隐藏状态模型的有意义的行为,而不是根据 getters/setters 来思考,这往往导致无法定位数据和行为,而这正是 OO 的主要意义所在。
#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
我喜欢“=”赋值的即时性,但我需要在多方法提供的辅助操作中轻松进行。我明白这是两个不同的世界,不能混为一谈。
但是 - 我不明白为什么我不能去 $c.v( 43 ) 设置 public 属性
- 我觉得 raku 引导我不要混合这两种模式 - 一些属性是私有的,一些是 public 并且压力是针对方法方法的(有一些:来自冒号的糖) - 是这个Raku的设计意图?
- 我是不是漏掉了什么?
BUT - I do not understand why I can’t just go $c.v( 43 ) To set a public attribute
好吧,这完全取决于架构师。但说真的,不,这根本不是 Raku 的标准工作方式。
现在,完全有可能在模块 space 中创建一个 Attribute
特征,类似于 is settable
,这将创建一个替代访问器方法 would 接受单个值来设置值。这样做的核心问题是,我认为世界上基本上有 2 个阵营关于这样一个突变器的 return 值: return new 值,或 旧 值?
如果您有兴趣在模块 space 中实现这样的特性,请与我联系。
我目前怀疑你只是感到困惑。1 在我谈及之前,让我们从你不感到困惑的地方重新开始:
I like the immediacy of the
=
assignment, but I need the ease of bunging in side actions that multi methods provide. ... I do not understand why I can’t just go$c.v( 43 )
To set a public attribute
你可以做所有这些事情。也就是说,如果你想同时使用 =
赋值、多种方法和 "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
一个可能的混淆来源1
在幕后,has $.foo is rw
生成一个属性和一个方法,如下所示:
has $!foo;
method foo () is rw { $!foo }
不过上面说的不太对。考虑到我们看到的行为,编译器自动生成的 foo
方法以某种方式声明,任何同名的 new 方法都会默默地 shadows 它.2
因此,如果您想要一个或多个与属性同名的自定义方法,您必须手动复制自动生成的方法,如果您希望保留通常的行为负责.
脚注
1 请参阅 jnthn 的回答,以清楚、透彻、权威地解释 Raku 关于私人与 public getters/setters 的观点以及它在幕后的作用当您声明 public getters/setters(即写 has $.foo
)时。
2 如果为属性声明了一个自动生成的访问器方法 only
,那么 Raku 会,我推测,如果一个具有相同名称的方法是宣布。如果它被声明为 multi
,那么如果新方法也被声明为 multi
,则不应隐藏它,否则应抛出异常。因此,自动生成的访问器的声明既没有 only
也没有 multi
而是以某种允许静默阴影的方式。
is this the intent of Raku's design?
可以说 Raku 在这方面并非完全没有主见。你的问题涉及Raku设计中的两个主题,都值得讨论一下。
Raku 有第一-class 个左值
Raku 大量使用左值作为第一个 class 事情。当我们写:
has $.x is rw;
生成的方法是:
method x() is rw { $!x }
这里的 is rw
表示该方法正在返回一个 l-value - 即可以分配给的东西。因此当我们写:
$obj.x = 42;
这不是语法糖:它实际上是一个方法调用,然后将赋值运算符应用于它的结果。这行得通,因为方法调用 returns 属性的 Scalar
容器,然后可以将其赋值进去。可以使用绑定将其分成两步,看看它不是一个微不足道的句法转换。例如,这个:
my $target := $obj.x;
$target = 42;
将分配给对象属性。许多其他功能都采用相同的机制,包括列表分配。例如,这个:
($x, $y) = "foo", "bar";
通过构造一个包含容器 $x
和 $y
的 List
来工作,然后赋值运算符在这种情况下对每一侧进行迭代以进行赋值。这意味着我们可以在那里使用 rw
对象访问器:
($obj.x, $obj.y) = "foo", "bar";
这一切都自然而然地起作用了。这也是分配给数组和散列的切片背后的机制。
也可以使用 Proxy
来创建一个左值容器,读写行为在您的控制之下。因此,您可以将副动作放入 STORE
。然而...
Raku 比 "setters"
更鼓励语义方法当我们描述 OO 时,经常会出现 "encapsulation" 和 "data hiding" 这样的术语。这里的关键思想是对象内部的状态模型——即它选择表示实现其行为(方法)所需数据的方式——可以自由发展,例如处理新需求。对象越复杂,就越自由。
然而,getters 和 setters 是与状态具有隐式连接的方法。虽然我们可能声称我们正在实现数据隐藏,因为我们正在调用一个方法,而不是直接访问状态,但我的经验是,我们很快就会到达一个地方,外部代码正在进行 setter 调用序列以实现操作 - 这是特征羡慕反模式的一种形式。如果我们正在做 that,可以肯定的是我们最终会在对象之外的逻辑中混合执行 getter 和 setter 操作实现一个操作。实际上,这些操作应该作为方法公开,其名称能够描述正在实现的目标。如果我们处于并发环境中,这一点就变得更加重要;设计良好的对象通常很容易在方法边界处得到保护。
也就是说,class
的许多用途实际上是 record/product 类型:它们的存在只是为了将一堆数据项简单地组合在一起。 .
印记不仅生成访问器,而且还生成:
- 选择由默认对象初始化逻辑设置的属性(即
class 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 隐藏状态模型的有意义的行为,而不是根据 getters/setters 来思考,这往往导致无法定位数据和行为,而这正是 OO 的主要意义所在。