如果集合如我所想
— 焉知非鱼If Sets Would DWIM
If Sets Would DWIM
每当我在 Raku 中使用集合的时候,它们经常无法 DWIM。这是一个简短的探索,看看是否可以改进 DWIMminess。
我最近重新审视了我前段时间写的一个利用 (-)
集差运算符的脚本。这段代码有一个 bug 潜伏在那里,显而易见,因为下面的代码并没有按照我的直觉去做。
my @allowed = <m c i p l o t>;
my @chars = 'impolitic'.comb;
my @remainder = @allowed (-) @chars;
if +@remainder == 0 {
say 'pangram';
} else {
say "unused: [{@remainder.join(' ')}]";
}
unused: []
错误的原因是 (-)
产生了一个 Set,而赋值给 @remainder
会产生1项的 Array。总是这样。但不方便的是,当它是一个空集合时,它就会字符串化为一个空字符串,这只是帮助掩盖了这个潜伏的错误。
my @items = <a b c d e> (-) <a b c d e>;
say @items.raku;
say +@items;
[Set.new()]
1
解决方法比较简单。只要不赋值给数组就可以了。使用一个标量容器来代替。
my $items = <a b c d e> (-) <a b d>;
say $items.raku;
say +$items;
Set.new("e","c")
2
甚至是关联容器也可以。
my %items = <a b c d e> (-) <a b d>;
say %items.raku;
say +%items;
{:c(Bool::True), :e(Bool::True)}
2
或在赋值前明确地取出键的列表。
my @items = (<a b c d e> (-) <a b d>).keys;
say @items.raku;
say +@items;
["e", "c"]
2
很好,起作用了。只是不要用数组容器来处理 Setty
这样的东西。只是这并不能阻止我的直觉时不时地碰上这个错误。同一类的 bug 在我的代码中出现过好几次,因为它实在是太容易犯错了。Raku 不会告诉我,我做错了什么,因为也许是故意的。但重要的是, Raku 没有设法 DWIM。
我可以采取的另一个方法是养成添加类型信息的习惯。这样确实可以让 Raku 在我掉进这个陷阱的时候告诉我。
my Str @a = <a b c d e> (-) <a b d>;
Type check failed in assignment to @a; expected Str but got Set (Set.new("e","c"))
in sub at EVAL_0 line 3
in block <unit> at EVAL_0 line 5
in block <unit> at -e line 1
这是一个明显的例子,添加类型信息有助于 Raku 编译器帮助我避免引入这种 bug。
实验 - 为 Set 自定义数组存储 #
我开始研究核心设置(core setting),看看可以做什么。我惊喜地发现,我可以在 Array.STORE
的多重分派中添加我正在寻找的语义。
use MONKEY;
augment class Array {
multi method STORE(Array:D: Set \item --> Array:D) {
self.STORE(item.keys)
}
}
my @a = <a b c d e> (-) <a b d>;
say @a.raku;
say +@a;
["c", "e"]
2
分享这个似乎是谨慎的,看看我的小 DWIM 是否有任何我没有考虑到的问题或缺点。一个可能的缺点是,如果你需要这样做的话,你需要使用 ,
来强制将一个集合变成一个数组。
my @a = <a b c d e> (-) <a b d> , ;
say @a.raku;
[Set.new("e","c")]
下一步是什么 #
我希望这能引发关于这个问题以及其他我们的直觉和 Raku 的行为不太一致的情况的讨论。也许还有其他相关的语言边缘可以被磨平,以消除这种危害。
后续 #
在 Reddit 上有一些非常有启发性的讨论,涵盖了语言语义和各种替代方法。公平地说,我建议的方法引入了更多的不一致性,而不是价值,但讨论可能会导致一个语言一致的解决方案。