如何防止 Nil 将容器恢复为默认值?
How to keep Nil from reverting container to its default value?
我正在实现一个简单的链表,为了表示没有下一个节点这一事实,我使用了值 Nil
。问题是,当分配给容器时,Nil
将尝试将容器恢复为默认值,这意味着我需要使用容器的默认值或 Any
来确定链表的末尾是否已到达。但是,我仍然想使用 Nil
(如果只是为了它的明确意图)并且没有其他值来测试代码中下一个节点的不存在(例如,until $current === Any
,last if $current-node === Any;
).对于某些上下文:
class Node {
has $.data is rw is required;
has $.next is rw = Nil;
}
class UnorderedList {
has $!head = Nil;
method add(\item --> Nil) {
my $temp = Node.new: data => item;
$temp.next = $!head;
$!head = $temp;
}
method size(--> UInt) {
my $current = $!head;
my UInt $count = 0;
until $current === Any { # <=== HERE
$count += 1;
$current .= next;
}
return $count;
}
method gist(--> Str) {
my $current-node = $!head;
my $items := gather loop {
last if $current-node === Any; # <=== HERE
take $current-node.data;
$current-node .= next;
}
$items.join(', ')
}
}
答案是:Nil
可以也是默认值。所以与其说:
has $.next is rw = Nil;
您所要做的就是:
has $.next is rw is default(Nil);
将 Nil
分配给这样的 rw 属性,将为您提供 Nil
。
一些选择,其中我认为最吸引人的是顶部,而我最反对的是底部:
使用Node
(你的Node
class的类型对象)。 这是 jnthn 推荐的选项。 这对所有熟悉 Raku 对已定义值和未定义值的区别的 rakuns 来说都是有意义的,如果您将 Node
类型约束添加到节点变量。 Imo 这将是惯用的解决方案。
使用Empty
。可以说,它具有与 Nil
相同的大部分积极因素,但没有我在本答案后面看到和讨论的大部分消极因素。如果你使用 Empty
(或 End
或其他),你可以创建一个 subset MaybeNode where Node | Empty
或任何允许向节点变量添加类型约束的东西。 (虽然这样的样板主要用于突出使用 Node
选项的简单优雅的人体工程学。)
使用is default(Nil)
设置节点属性的默认值。 这是 Liz 建议的选项。 同样,可以使用 is default(Empty)
作为使用 Node
以外的任何其他选项的一部分。
选择一个新名称。 None
可能是一个糟糕的选择,因为它在许多具有其他语义的语言中具有既定含义。也许 End
? (声明为 role End {}
。)
声明你自己的 Nil
并在词法上隐藏 Raku 的内置 Nil
。我会说那是一件疯狂的事情;如果我要对你的代码进行同行评审,我会反对它;并且会预料到大多数 rakuns 都会同意我的观点;但是你可以.
选项的进一步讨论
根据Nil
's doc,它的定义是:
Absence of a value or a benign failure
我觉得这个重载是 Larry 的天才。但最好尊重它的本来面目。这不仅仅是一个超载的语义,而且 明确地 如此。继续文档:
Failure
is derived from Nil
, so smartmatching Nil
will also match Failure
. ... Along with Failure
, Nil
and its subclasses may always be returned from a routine even when the routine specifies a particular return type [even] regardless of the definedness of the return type.
所有 这些语义将在思想上和实际上应用于您的代码,即使您不想要它们。您绝对确定您想要它们吗?请参阅我在此答案下方的评论中对@ElizabethMattijsen 的第一个回复,以简要讨论我认为这可能导致的问题。另一个是人们 阅读 您的代码可能 思考 同样的麻烦。这可以说是不必要的开销。
另一方面,即使在相当复杂的代码中,这样的麻烦(实际的而不是想象的)也不太可能发生。而且您的代码非常简单。因此,对于您提供的场景,我对这个失败方面的关注被夸大了。 Liz 的解决方案简单又好。
在抓手方面,即使对于最复杂的代码,采用最普遍适用的简单解决方案也很好,并让新手尽早接触这些简单和通用的解决方案。您也可能有兴趣了解选项范围。当然,我写的东西可能是错误的,并得到很好的反馈。因此这个答案。
I'd still like to use Nil
(if only for its clear intent)
如果代码只是给你看的,那么你看清楚了就由你来决定了。
我认为 Nil
的明确意图对于那些了解 Raku 的人来说是象征着 Raku 的 Nil
。
Raku 的 Nil
具有我上面引用的特定语义。
如果有人将注意力集中在“缺乏价值”上,您的解决方案将显得非常笨拙。他们关注“良性失败”的程度,并考虑这种语义在整个 Raku 中的使用方式,他们可能会大吃一惊。显然,到达没有下一个节点的节点非常适合“良性故障”和“无价值”。事实上,这就是 Larry 使它们超载的原因。但是 其他代码 也使用 Nil
来表示这些东西,它们可能并不表示“link 列表的结尾”。
类似的混合故事适用于 Node
和 Empty
,但风格不同。
Node
很有意义,因为它在 Raku 中是惯用的,类型对象是未定义的,你可以键入约束节点变量,它会接受 Node
。但它也是一种表示未初始化的东西的惯用方式。你真的想要那个超载的语义吗?也许还好,也许不好。
Empty
是有道理的,因为它读起来很好,像 Nil
和 Node
一样是未定义的(但没有其他不需要的语义),并且错误地遇到它的机会正在消失小的。它的缺点是它错过了 Node
对于已定义/未定义值的良好惯用二元性;它不像新的本地定义名称(例如 End
)那么安全;有人可能想知道为什么您的代码使用 Slip
;根据您的偏好,它的定义不会像 Nil
那样吸引您。
这有点像在问“打着伞我头上怎么还有雨”。 :-) Nil
存在于 Raku 中的主要原因是提供一个函数可以 return 在软故障产生结果时的值,如果分配到任何支持未定义的容器中,这将是安全的(或默认)。
因此,可以编写一个可以 return Nil
的函数 func
,它只适用于调用者 my SomeType $foo = func()
或调用者 [=22] =].
虽然有 is default(Nil)
技巧,但我强烈建议您使用其他值作为标记。在您不希望它提供的主要行为的情况下尝试使用语言功能通常不会顺利进行。
Node
类型对象本身就是一个合理的选择。因此:
has $!head = Nil;
变为:
has Node $!head;
测试变为:
until $current === Node {
不过,我可能会这样写:
while $current.defined {
它还支持 Node
的子classing。另一方面,如果我知道 Node
是我的 class 的实现细节,我会觉得足够安全,可以使用依赖于默认的 boolification 语义来消除混乱:
while $current {
同时,这:
last if $current-node === Any;
可能会变成:
last without $current-node;
或者如果选择“依赖布尔化”方法,为了保持一致性:
last unless $current-node;
最后,如果 Node
只是一个实现细节,我会把它移到 UnorderedList
内并使其成为 my
范围。
我正在实现一个简单的链表,为了表示没有下一个节点这一事实,我使用了值 Nil
。问题是,当分配给容器时,Nil
将尝试将容器恢复为默认值,这意味着我需要使用容器的默认值或 Any
来确定链表的末尾是否已到达。但是,我仍然想使用 Nil
(如果只是为了它的明确意图)并且没有其他值来测试代码中下一个节点的不存在(例如,until $current === Any
,last if $current-node === Any;
).对于某些上下文:
class Node {
has $.data is rw is required;
has $.next is rw = Nil;
}
class UnorderedList {
has $!head = Nil;
method add(\item --> Nil) {
my $temp = Node.new: data => item;
$temp.next = $!head;
$!head = $temp;
}
method size(--> UInt) {
my $current = $!head;
my UInt $count = 0;
until $current === Any { # <=== HERE
$count += 1;
$current .= next;
}
return $count;
}
method gist(--> Str) {
my $current-node = $!head;
my $items := gather loop {
last if $current-node === Any; # <=== HERE
take $current-node.data;
$current-node .= next;
}
$items.join(', ')
}
}
答案是:Nil
可以也是默认值。所以与其说:
has $.next is rw = Nil;
您所要做的就是:
has $.next is rw is default(Nil);
将 Nil
分配给这样的 rw 属性,将为您提供 Nil
。
一些选择,其中我认为最吸引人的是顶部,而我最反对的是底部:
使用
Node
(你的Node
class的类型对象)。 这是 jnthn 推荐的选项。 这对所有熟悉 Raku 对已定义值和未定义值的区别的 rakuns 来说都是有意义的,如果您将Node
类型约束添加到节点变量。 Imo 这将是惯用的解决方案。使用
Empty
。可以说,它具有与Nil
相同的大部分积极因素,但没有我在本答案后面看到和讨论的大部分消极因素。如果你使用Empty
(或End
或其他),你可以创建一个subset MaybeNode where Node | Empty
或任何允许向节点变量添加类型约束的东西。 (虽然这样的样板主要用于突出使用Node
选项的简单优雅的人体工程学。)使用
is default(Nil)
设置节点属性的默认值。 这是 Liz 建议的选项。 同样,可以使用is default(Empty)
作为使用Node
以外的任何其他选项的一部分。选择一个新名称。
None
可能是一个糟糕的选择,因为它在许多具有其他语义的语言中具有既定含义。也许End
? (声明为role End {}
。)声明你自己的
Nil
并在词法上隐藏 Raku 的内置Nil
。我会说那是一件疯狂的事情;如果我要对你的代码进行同行评审,我会反对它;并且会预料到大多数 rakuns 都会同意我的观点;但是你可以.
选项的进一步讨论
根据Nil
's doc,它的定义是:
Absence of a value or a benign failure
我觉得这个重载是 Larry 的天才。但最好尊重它的本来面目。这不仅仅是一个超载的语义,而且 明确地 如此。继续文档:
Failure
is derived fromNil
, so smartmatchingNil
will also matchFailure
. ... Along withFailure
,Nil
and its subclasses may always be returned from a routine even when the routine specifies a particular return type [even] regardless of the definedness of the return type.
所有 这些语义将在思想上和实际上应用于您的代码,即使您不想要它们。您绝对确定您想要它们吗?请参阅我在此答案下方的评论中对@ElizabethMattijsen 的第一个回复,以简要讨论我认为这可能导致的问题。另一个是人们 阅读 您的代码可能 思考 同样的麻烦。这可以说是不必要的开销。
另一方面,即使在相当复杂的代码中,这样的麻烦(实际的而不是想象的)也不太可能发生。而且您的代码非常简单。因此,对于您提供的场景,我对这个失败方面的关注被夸大了。 Liz 的解决方案简单又好。
在抓手方面,即使对于最复杂的代码,采用最普遍适用的简单解决方案也很好,并让新手尽早接触这些简单和通用的解决方案。您也可能有兴趣了解选项范围。当然,我写的东西可能是错误的,并得到很好的反馈。因此这个答案。
I'd still like to use
Nil
(if only for its clear intent)
如果代码只是给你看的,那么你看清楚了就由你来决定了。
我认为 Nil
的明确意图对于那些了解 Raku 的人来说是象征着 Raku 的 Nil
。
Raku 的 Nil
具有我上面引用的特定语义。
如果有人将注意力集中在“缺乏价值”上,您的解决方案将显得非常笨拙。他们关注“良性失败”的程度,并考虑这种语义在整个 Raku 中的使用方式,他们可能会大吃一惊。显然,到达没有下一个节点的节点非常适合“良性故障”和“无价值”。事实上,这就是 Larry 使它们超载的原因。但是 其他代码 也使用 Nil
来表示这些东西,它们可能并不表示“link 列表的结尾”。
类似的混合故事适用于 Node
和 Empty
,但风格不同。
Node
很有意义,因为它在 Raku 中是惯用的,类型对象是未定义的,你可以键入约束节点变量,它会接受 Node
。但它也是一种表示未初始化的东西的惯用方式。你真的想要那个超载的语义吗?也许还好,也许不好。
Empty
是有道理的,因为它读起来很好,像 Nil
和 Node
一样是未定义的(但没有其他不需要的语义),并且错误地遇到它的机会正在消失小的。它的缺点是它错过了 Node
对于已定义/未定义值的良好惯用二元性;它不像新的本地定义名称(例如 End
)那么安全;有人可能想知道为什么您的代码使用 Slip
;根据您的偏好,它的定义不会像 Nil
那样吸引您。
这有点像在问“打着伞我头上怎么还有雨”。 :-) Nil
存在于 Raku 中的主要原因是提供一个函数可以 return 在软故障产生结果时的值,如果分配到任何支持未定义的容器中,这将是安全的(或默认)。
因此,可以编写一个可以 return Nil
的函数 func
,它只适用于调用者 my SomeType $foo = func()
或调用者 [=22] =].
虽然有 is default(Nil)
技巧,但我强烈建议您使用其他值作为标记。在您不希望它提供的主要行为的情况下尝试使用语言功能通常不会顺利进行。
Node
类型对象本身就是一个合理的选择。因此:
has $!head = Nil;
变为:
has Node $!head;
测试变为:
until $current === Node {
不过,我可能会这样写:
while $current.defined {
它还支持 Node
的子classing。另一方面,如果我知道 Node
是我的 class 的实现细节,我会觉得足够安全,可以使用依赖于默认的 boolification 语义来消除混乱:
while $current {
同时,这:
last if $current-node === Any;
可能会变成:
last without $current-node;
或者如果选择“依赖布尔化”方法,为了保持一致性:
last unless $current-node;
最后,如果 Node
只是一个实现细节,我会把它移到 UnorderedList
内并使其成为 my
范围。