第二十二天-Raku.d 的特性

Day 22 – Features of Raku.d

Day 22 – Features of Raku.d

所以我们就是这样。 Rakudo Raku第一次正式发布两年后,或者更准确的说是6.c。自从马特奥茨从那时起就开始关注性能的提升之后,圣诞老人认为要对此进行对比,描述自那时起实施的6.d的新功能。因为有很多,圣诞老人不得不做出选择。

在创建时调整对象

您创建的任何课程现在都可以使用TWEAK方法。在新的类的新实例的所有其他初始化完成之前,这个方法将被调用。一个简单的,有点人为的例子,其中一个类A有一个属性,默认值是42,但如果在创建对象时指定了默认值,它应该更改该值:

class A {
    has $.value = 42;
    method TWEAK(:$value = 0) { # default prevents warning
        # change the attribute if the default value is specified
        $!value = 666 if $value == $!value;
    }
}
# no value specified, it gets the default attribute value
dd A.new;              # A.new(value => 42)

# value specified, but it is not the default
dd A.new(value => 77); # A.new(value => 77)

# value specified, and it is the default 
dd A.new(value => 42); # A.new(value => 666)

并发性改进

Rakudo Raku的并发功能在引擎盖下看到了许多改进。其中一些暴露为新功能。最显着的是Lock :: Async(一个返回Promise的非阻塞锁)和原子操作符。

在大多数情况下,您不需要直接使用它们,但是如果您正在编写使用并发功能的程序,那么您可能知道原子操作符。经常发生的逻辑错误,特别是如果你在Pumpking Perl 5中使用线程,是因为在Rakudo Raku中没有对共享变量的隐式锁定。例如:

my int $a;
    await (^5).map: {
        start { ++$a for ^100000 }
    }
    say $a; # something like 419318

那么为什么没有显示500000?原因是我们有5个线程同时递增相同的变量。由于增量由读步骤,增量步骤和写步骤组成,因此一个线程与另一个线程同时执行读取步骤变得非常容易。因此失去了一个增量。在我们有原子操作符之前,做上述代码的正确方法是:

   my int $a;
    my $l = Lock.new;
    await (^5).map: {
       start {
           for ^100000 {
               $l.protect( { ++$a } )
           }
       }
    }
    say $a; # 500000

这会给你正确的答案,但速度至少要慢20倍。

现在我们有了原子变量,上面的代码就变成了:

   my atomicint $a;
    await (^5).map: {
        start { ++⚛$a for ^100000 }
    }
    say $a; # 500000

这非常类似于原始(不正确)的代码。这至少是使用Lock.protect的正确代码的6倍。

Unicode goodies

太多了,太多了。例如,现在可以使用≤,≥,≠作为Unicode版本的<=,> =和!=(完整列表)。

您现在还可以通过指定字形的Unicode名称来创建字形,例如:

say "BUTTERFLY".parse-names; # 🦋

或者在运行时创建Unicode名称字符串:

my $t = "THUMBS UP SIGN, EMOJI MODIFIER FITZPATRICK TYPE";
print "$t-$_".parse-names for 3..6; # 👍🏼👍🏽👍🏾👍🏿

或者整理而不是仅仅排序:

# sort by codepoint value
say <ä a o ö>.sort; # (a o ä ö)
# sort using Unicode Collation Algorithm
say <ä a o ö>.collate; # (a ä o ö)

或者使用unicmp而不是cmp:

say "a" cmp "Z"; # More
say "a" unicmp "Z"; # Less

或者您现在可以使用任何Unicode数字匹配变量($ 1为$ 1),负数(-1为-1)和基数基数(:3(“22”)为3(“22”))。

圣诞老人认为Rakudo Raku拥有世界上任何编程语言的最佳Unicode支持!

跳过值

您现在可以在Seq和Supply上调用.skip跳过正在生成的多个值。与.head和.tail一起,这给了你Iterables和Supplies充足的操作性。

顺便说一下,.head现在也会带一个WhateverCode,所以你可以指明除了最后N以外的所有值(例如.head(* - 3)会给你除了最后三个以外的所有值)。 .tail也是如此(例如.tail(* - 3)会为您提供除前三个之外的所有值)。

对迭代器角色的一些补充使得迭代器可以更好地支持.skip功能。如果一个迭代器可以更有效地跳过一个值而不是实际产生它,它应该实现skip-one方法。派生于此的是可以由迭代器提供的跳过至少和跳过至少拉一个方法。

使用.skip查找第1000个素数的示例:

say (^Inf).grep(*.is-prime)[999]; # 7919

say (^Inf).grep(*.is-prime).skip(999).head; # 7919

后者的CPU效率略高一些,但更重要的是内存效率更高,因为它不需要保留内存中的前999个素数。

Of Bufs and Blobs

Buf变得更像一个Array,因为它现在支持.push,.append,.pop,.unshift,.prepend,.shift和.splice。它也变得更像Str,增加了一个subbuf-rw(类似于.substr-rw),例如:

my $b = Buf.new(100..105);
$b.subbuf-rw(2,3) = Blob.new(^5);
say $b.perl; # Buf.new(100,101,0,1,2,3,4,105)

您现在也可以使用给定数量的元素和模式来分配Buf或Blob。或者用.reallocate改变Buf的大小:

my $b = Buf.allocate(10,(1,2,3));
say $b.perl; # Buf.new(1,2,3,1,2,3,1,2,3,1)
$b.reallocate(5);
say $b.perl; # Buf.new(1,2,3,1,2)

测试,测试,测试!

Test.pm的计划子例程现在还采用可选的:skip-all参数来指示文件中的所有测试都应该跳过。或者您可以拨打救助中止测试运行,将其标记为失败。或者将PERL6_TEST_DIE_ON_FAIL环境变量设置为真值,以指示您希望测试一旦第一次测试失败就立即结束。

这是怎么回事

您现在可以通过调用Kernel.cpu-cores来反思计算机中CPU内核的数量。程序启动后使用的CPU数量在Kernel.cpu-usage中可用,但您可以使用VM.osname轻松检查操作系统的名称。

就好像这还不够,还有一个新的遥测模块,您需要在需要时加载,就像测试模块一样。遥测模块提供了许多可直接使用的基元,例如:

use Telemetry;
say T<wallclock cpu max-rss>; # (138771 280670 82360)

它显示自程序启动以来的微秒数,所用CPU的微秒数以及调用时正在使用的内存数量。

如果你想得到你的程序中发生的事情的报告,你可以使用管理单元,并在程序完成时显示报告。例如:

use Telemetry;
snap;
Nil for ^10000000;  # something that takes a bit of time

结果将显示在STDERR上:

Telemetry Report of Process #60076
Number of Snapshots: 2
Initial/Final Size: 82596 / 83832 Kbytes
Total Time:           0.55 seconds
Total CPU Usage:      0.56 seconds
No supervisor thread has been running

wallclock  util%  max-rss
   549639  12.72     1236
--------- ------ --------
   549639  12.72     1236

Legend:
wallclock  Number of microseconds elapsed
    util%  Percentage of CPU utilization (0..100%)
  max-rss  Maximum resident set size (in Kbytes)

如果你想要每秒1次的程序状态,你可以使用snapper:

use Telemetry;
snapper;
Nil for ^10000000;  # something that takes a bit of time

结果:

Telemetry Report of Process #60722
Number of Snapshots: 7
Initial/Final Size: 87324 / 87484 Kbytes
Total Time:           0.56 seconds
Total CPU Usage:      0.57 seconds
No supervisor thread has been running

wallclock  util%  max-rss
   103969  13.21      152
   101175  12.48
   101155  12.48
   104097  12.51
   105242  12.51
    44225  12.51        8
--------- ------ --------
   559863  12.63      160

Legend:
wallclock  Number of microseconds elapsed
    util%  Percentage of CPU utilization (0..100%)
  max-rss  Maximum resident set size (in Kbytes)

还有更多选项可用,例如以.csv格式获取输出。

MAIN 函数

您现在可以通过设置%* SUB-MAIN-OPTS中的选项来修改MAIN参数的处理方式。默认的USAGE消息现在可以在MAIN中作为$ * USAGE动态变量使用,所以如果你愿意,你可以改变它。

嵌入 Raku

两个新功能使嵌入Rakudo Raku更易于处理: 现在可以设置&* EXIT动态变量来指定调用exit()时要执行的操作。

将环境变量RAKUDO_EXCEPTIONS_HANDLER设置为“JSON”将引发JSON中的异常,而不是文本,例如:

$ RAKUDO_EXCEPTIONS_HANDLER=JSON raku -e '42 = 666'
{
  "X::Assignment::RO" : {
    "value" : 42,
    "message" : "Cannot modify an immutable Int (42)"
  }
}

礼品袋的底部

在翻看仍然相当完整的礼品袋的同时,圣诞老人发现了以下较小的惊悚片:

本地字符串数组现在实现(我的str @a) IO :: CatHandle允许您将多个数据源抽象为单个虚拟IO :: Handle parse-base()执行base()的相反操作

赶上雪橇的时间

圣诞老人想留下来告诉你更多有关已添加的内容,但是没有足够的时间来做到这一点。如果您真的想了解新功能的最新情况,您应该查看Changelog中的Additions部分,这些部分随每个Rakudo编译器版本一起更新。

所以,明年再来抓你!

来自美好的祝福

🎅🏾

Raku 

comments powered by Disqus