如何防止 Nil 将容器恢复为其默认值?

uzl*_*xxx 13 nodes data-structures raku

我正在实现一个简单的链表并表示没有下一个节点的事实,我使用了 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(', ')
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*ton 10

这有点像问“我打伞的时候怎么还会有雨落在我的头上”。:-) NilRaku 中存在的主要原因是提供一个函数可以在软故障时返回的值以产生结果,如果分配到任何支持未定义(或默认)的容器中,这将是安全的。

因此,可以编写一个func可以返回的函数Nil,并且它可以为调用者my SomeType $foo = func()或调用者工作my OtherType $bar = func()

虽然有is default(Nil)诀窍,但我强烈建议使用其他值作为您的哨兵。在您不希望它提供的主要行为的情况下尝试使用语言功能通常不会顺利进行。

Node类型的对象本身将是一个合理的选择。因此这个:

has $!head = Nil;
Run Code Online (Sandbox Code Playgroud)

变成:

has Node $!head;
Run Code Online (Sandbox Code Playgroud)

测试变成:

until $current === Node {
Run Code Online (Sandbox Code Playgroud)

不过,我可能会写成:

while $current.defined {
Run Code Online (Sandbox Code Playgroud)

它还支持Node. 另一方面,如果我知道Node是我的类的实现细节,我会觉得足够安全,可以依赖默认的布尔化语义来消除混乱:

while $current {
Run Code Online (Sandbox Code Playgroud)

同时,这:

last if $current-node === Any;
Run Code Online (Sandbox Code Playgroud)

可以变成:

last without $current-node;
Run Code Online (Sandbox Code Playgroud)

或者为了一致性,如果选择“依赖布尔化”方​​法:

last unless $current-node;
Run Code Online (Sandbox Code Playgroud)

最后,如果Node只是一个实现细节,我会将它移到内部UnorderedList并使其成为my范围。


rai*_*iph 8

一些选择,我认为最吸引顶部的选择和底部的我会积极劝阻的选择:

  • 使用Node(你的Node类的类型对象)。 这是 jnthn 推荐的选项。这对所有熟悉 Raku 定义和未定义值之间区别的 rakun 都有意义,并且如果您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. 我会说这是一件很疯狂的事情。如果我要对你的代码进行同行评审,我会反对它;并且预计大多数 rakun 都会同意我的看法;但你可以

选项的进一步讨论

PerNil的 doc,它的定义是:

缺少值或良性故障

我觉得这种超载是拉里的天才。但它是最好的尊重它是什么。这不仅仅是一个重载的语义,而是明确的。继续文档:

Failure派生自Nil,所以 smartmatchingNil也会匹配Failure。...连同Failure,Nil及其子类可能总是从例程返回,即使例程指定了特定的返回类型 [偶数],而不管返回类型的定义如何。

即使您不想要它们,所有这些语义都会在精神上和实际上应用于您的代码。你确定你想要它们吗?请参阅我在此答案下方的评论中对 @ElizabethMattijsen 的第一次回复,以简要讨论我建议这可能导致的麻烦。另一个是阅读您的代码的人可能会想到同样的麻烦。这可以说是不必要的开销。

另一方面,即使在相当复杂的代码中,这种麻烦(实际的而不是想象的)也不太可能发生。而且您的代码非常简单。因此,对于您提供的场景,我对这个失败方面的关注被夸大了。Liz 的解决方案既简单又好用。

在抓手方面,采用最普遍适用的简单解决方案也是很好的,即使是最复杂的代码,并尽早让新手接触这些简单和通用的解决方案。此外,您可能有兴趣了解选项范围。当然,我可能对我写的内容有误并得到很好的反馈。因此这个答案。

我仍然想使用Nil(如果只是为了它的明确意图)

如果代码只适合您的眼睛,那么您认为清楚的内容由您决定。

ImoNil对那些认识 Raku 的人的明确意图是象征 Raku 的Nil

RakuNil具有我上面引用的特定语义。

如果有人将注意力集中在“缺乏价值”上,那么您的解决方案将看起来非常繁琐。如果他们专注于“良性失败”,并考虑如何在整个 Raku 中使用这种语义,他们可能会皱眉。显然,到达没有下一个节点的节点非常适合“良性故障”和“缺乏价值”。事实上,这就是拉里让他们超负荷的原因。但是其他代码Nil用来表示这些东西,它们可能并不意味着“链接列表的结尾”。

类似的混合故事适用于NodeEmpty,但具有不同的风味。

Node很有意义,因为在 Raku 中,类型对象未定义是惯用的,您可以输入约束节点变量,它会接受Node. 但这也是表示未初始化内容的惯用方式。你真的想要那种重载的语义吗?也许很好,也许不是。

Empty是有道理的,因为它读起来很好,像Niland一样未定义Node(但没有其他不需要的语义),并且错误地遇到它的机会非常小。它的缺点是它错过了Node用于已定义/未定义值的良好惯用二元性;它不像本地定义的新名称(例如End)那样超级安全;有人可能想知道为什么您的代码使用Slip; 并且根据定义,它不会像Nil您陈述的偏好那样对您有吸引力。