贪婪 Junction 的奇闻异事
— 焉知非鱼The Strange Case of the Greedy Junction
贪婪 junction 的奇闻异事 #
说明 Raku 的 junction 是如何贪婪的设计,以及一个建议。
Raku 有一个整洁的功能,叫做 Junction。在这篇短文中,我想强调一下 junction 与函数交互的一个特殊后果:它们是贪婪的,我的意思是它们会无意中把函数的其他参数变成 junction。为了说明这种行为,我将使用一个闭包创建一个 pair
数据结构,它可以接受两个不同类型的值。
enum RGB <R G B>;
# Pair Constructor: the arguments of pair() are captured
# in a closure that is returned
sub pair(\x, \y) {
sub (&p){ p(x, y) }
}
所以 pair
接受两个任意类型的参数,并返回一个以函数为参数的闭包。我们将使用这个函数来访问存储在 pair
中的值。我将把这些访问(accessor)函数称为 fst
和 snd
。
# Accessors to get the values from the closure
my sub fst (&p) {p( sub (\x,\y){x})}
my sub snd (&p) {p( sub (\x,\y){y})}
做实际选择的函数是由 fst
和 snd
返回的匿名子程序,这纯粹是为了让我可以将它们应用于 pair
,而不是必须将它们作为参数传递。让我们看一个例子,一个 Int
和一个 RGB
的 pair。
my \p1 = pair 42, R;
if ( 42 == fst p1) {
say snd p1; #=> says "R"
}
所以我们用两个值调用 pair
来创建一个 pair,并使用 fst
和 snd
来访问 pair 中的值。这是一个不可变的数据结构,所以不可能进行更新。
现在让我们使用 junction 作为其中一个参数。
# Example instance with a 'one'-type junction
my Junction \p1j = pair (42^43),R;
if ( 42 == fst p1j) {
say snd p1j; #=> one(R, R)
}
这里发生的情况是,原始参数 R
已经不可逆转地变成了与自己的 junction,尽管我们从未明确地在 R
上创建过 junction,但还是发生了这种情况。这是将 junction 类型应用于函数的结果,它不是一个 bug,只是 junction 行为的一个影响。更详细的解释,请看我的文章"重构 Raku 的 Junction"。
Raku 关于 junction 的文档中说,你不应该真正尝试从 junction 中获取值。
“Junction 是用来作为布尔上下文中的匹配器,不支持 junction 的自省。如果你觉得有自省 junction 的冲动,请使用 Set 或相关类型代替。”
然而,有一个 FAQ 勉强地告诉你如何做。FAQ 再次警告不要这样做。
“如果你想从 junction 中提取值(特征态),你可能做错了什么,应该用 Set 来代替。”
然而,正如我所举的例子所证明的那样,从 junction 中恢复值是有明确的用例的。当然,仅仅因为另一个值恰好是 junction,存储在 pair 中的其中一个值就变得不可访问,这不是我们的本意。
因此,我建议增加一个折叠(collapse
)函数,允许将这些无意中出现的 junction 值折叠成它们的原始值。
if ( 42 == fst p1j) {
say collapse(snd p1j); #=> says 'R'
}
该函数的实现取自上述常见问题,并增加了一个检查,以确保 junction 上的所有值都相同。
sub collapse(Junction \j) {
my @vvs;
-> Any \s { push @vvs, s }.(j);
my $v = shift @vvs;
my @ts = grep {!($_ ~~ $v)}, @vvs;
if (@ts.elems==0) {
$v
} else {
die "Can't collapse this Junction: elements are not identical: {$v,@vvs}";
}
}
如果能把这个功能作为一个 collapse
方法添加到 Junction
类中就更好了。
原文链接: https://gist.github.com/wimvanderbauwhede/85fb4b88ec53a0b8149e6c05740adcf8