摆脱模板

楽土

我同意 Damian 的观点,link:https://youtu.be/fVnmYzJfy5s?t=66[嫉妒显然是一种美德]。我们应该把自夸也加到这个名单上。如果我们从不告诉别人,那么我们可以不费吹灰之力就把事情做得很简单,这有什么好处呢?因此有了这篇博客。

在研究 Shell::Piping 的时候,我意识到许多语言都使用操作符或操作符重载来摆脱大量的模板。请允许我夸夸其谈地说明一下。

my $find = Proc::Async.new('/usr/bin/find', '/tmp');
my $sort = Proc::Async.new('/usr/bin/sort', :w);

$find.stdout.lines.tap: -> $l {
    once await $sort.ready;
    $sort.write: „$l\n“.encode if $l ~~ /a/;
}
my $handle-sort-output = start { put $sort.stdout.lines.join(„\n“); }
my $sort-started = $sort.start;
{
    await $find.start;
    CATCH { default { } }
}
$sort.close-stdin;

await $handle-sort-output;

所以我们基本上是 find /tmp | grep "a" | sort。Sort 有点不寻常,因为它要等 STDIN 被关闭后才开始做任何事情。我们不使用 grep,而是自己做过滤。如果我们不会的话,我们可以直接掏钱,省去我们的麻烦。我找到了一个用更少的代码来做同样的事情的方法。

$find |> -> $l { $l ~~ /a/ ?? $l !! Nil } |> $sort :quiet;

纵观 Raku 的link:https://docs.raku.org/language/operators[运算符],这种模式随处可见。尤其是超运算符,用一个表达式取代了一个循环和/或一连串的方法调用。下标也是如此。

my %a = flat (1..12) Z <January February March April May June July August September October November December>;
say %a<3 5 7>;
# OUTPUT: (March May July)

这里后环缀运算符遍历 3、5、7,并对每个元素调用 %a 上的 .AT-KEY。结果以产生的值的列表形式返回。如果我们要动手来做,6行代码就够了。

Proc::Async 的一个实例只能运行一次。当在 Raku 中使用 shell 脚本时,这可能会成为一种负担。我需要一种方法来声明一个例程(或者行为像例程的东西),它将从相同的参数集创建对象。我的目标是以下几点。

Shell::<&ls> = px<ls -l>;
Shell::<&grep> = px<grep>;
Shell::ls |> Shell::grep('a');

如果我没有踩到一个 link:https://github.com/rakudo/rakudo/issues/3799[bug] 的话,这个是很容易实现的。所以现在 px <grep> 中需要多一个空格。当我在做这件事的时候,我在 px 中增加了一些错误检查。如果相关文件不在那里,也不能执行,那么尝试启动 shell 命令就没有什么意义。

然而,简单的错误处理很容易添加,因为我依靠 infix 来构建管道。与后环缀相比,它们得到了 Rakudo 的良好支持。

multi sub handle-stderr(|) { };
multi sub handle-stderr(0, $line) { say „ERR stream find: $line“ };
$find |> $errorer |> $sort :done({ say .exitcode if .exitcode }) :stderr(&handle-stderr);

副词 :stderrShell::Pipe 中注册了一个回调,当管道成员中的任何 STDERR 的行时,该回调就会被调用。作为第一个参数,该回调接收产生该行的命令的位置。通过使用 multi,我们可以将正确的处理程序的选择卸载给编译器。在签名中的单个 | 声明一个默认的候选者,它将捕获所有其他未处理的输出到 STDERR。操作符在这里并没有什么作用。

my multi infix:«|>»(Shell::Pipe:D $pipe where $pipe.pipees.tail ~~ Shell::Pipe::BlockContainer, Proc::Async:D $in, :&done? = Code, :$stderr? = CodeOrChannel, Bool :$quiet    ?) {
     ...
     $pipe.done = &done;
     $pipe.stderr = $stder;
     $pipe.quiet = $quiet;
     ...
}

它只是将 Shell::Pipe 对象的一个公共属性设置为提供的回调。所以我在这里使用的模式其实很简单。使用一个 infix 将两个操作数变成一个中间类型。如果在该类型上使用了更多的 infix,则对其状态进行添加。副词用来触发可选行为。然后,编译器将在该中介上调用 .sink 来设置任何动作。正如本篇博文中的第一个例子所显示的那样,运动实际上可以是相当复杂的。然而我们可以通过定义一个自定义的操作符,将其隐藏在非常粗略的语法背后。

我设法实现了任何我需要的东西,开始把整个事情变成一个模块。对该模块的自动测试将是一个小小的挑战。幸运的是,正如 jnthn 善意地link:https://colabti.org/irclogger/irclogger_log/raku?date=2020-07-14#l107[指出]的那样,Raku 很适合测试崩溃。

从link:https://rakudoweekly.blog/2020/07/13/2020-28-bridges-7/[周刊]的情况来看,我们正在制作更多的博客文章,然后是前所未有的。我欢迎这个举动。我们没有理由对 Raku 谦虚。去吧,去炫耀吧!

comments powered by Disqus