erlang 从内部更改"wherefore“块的目标

2ekbmq32  于 2022-12-08  发布在  Erlang
关注(0)|答案(1)|浏览(147)

下面的代码尝试对一个Supply做出React,然后根据一些消息的内容改变主意,对来自另一个Supply的消息做出React。它试图提供与Supply.migrate类似的行为,但具有更多的控制。

  1. my $c1 = Supplier.new;
  2. my $c2 = Supplier.new;
  3. my $s = supply {
  4. my $currently-listening-to = $c1.Supply;
  5. my $other-var = 'foo';
  6. whenever $currently-listening-to {
  7. say "got: $_";
  8. if .starts-with('3') {
  9. say "listening to something new";
  10. $currently-listening-to = $c2.Supply;
  11. $other-var = 'bar';
  12. say $other-var;
  13. }
  14. }
  15. }
  16. $s.tap;
  17. for ^7 {
  18. $c1.emit: "$_ from \$c1";
  19. $c2.emit: "$_ from \$c2";
  20. }
  21. sleep 10;

如果我正确理解了supply块的语义(非常值得怀疑!),这个块应该对supply块中声明的任何变量都有独占和可变的访问权限。因此,我期望它从$c1中获取前4个值,然后切换到$c2。然而,它没有。下面是输出:

  1. ot: 0 from $c1
  2. got: 1 from $c1
  3. got: 2 from $c1
  4. got: 3 from $c1
  5. listening to something new
  6. bar
  7. got: 4 from $c1
  8. got: 5 from $c1
  9. got: 6 from $c1

如输出所示,更改$other-var的工作正如我所期望的那样,但是更改$currently-listening-to的尝试失败了(没有提示)。
这种行为是否正确?如果是,我在解释这种行为的supply块/其他结构的语义方面遗漏了什么?我在使用react块和使用Channel而不是Supply时得到了相同的结果,因此这种行为在多个并发结构中是一致的。
(In为了避免X-Y问题,触发这个问题的用例是尝试实现Erlang风格的错误处理。为此,我希望有一个监督supply块,它监听其子块,并可以杀死/重新启动任何进入坏状态的子块。但这意味着监听新的子块-这直接导致了上述问题。)

balp4ylt

balp4ylt1#

I tend to consider whenever as the reactive equivalent of for . (It even supports the LAST loop phaser for doing something when the tapped Supply is done , as well as supporting next , last , and redo like an ordinary for loop!) Consider this:

  1. my $x = (1,2,3);
  2. for $x<> {
  3. .say;
  4. $x = (4,5,6);
  5. }

The output is:

  1. 1
  2. 2
  3. 3

Because at the setup stage of a for loop, we obtain an iterator, and then work through that, not reading $x again on each iteration. It's the same with whenever : it taps the Supply and then the body is invoked per emit event.
Thus another whenever is needed to achieve a tap of the next Supply , while simultaneously closing the tap on the current one. When there are just two Supply s under consideration, the easy way to write it is like this:

  1. my $c1 = Supplier.new;
  2. my $c2 = Supplier.new;
  3. my $s = supply {
  4. whenever $c1 {
  5. say "got: $_";
  6. if .starts-with('3') {
  7. say "listening to something new";
  8. # Tap the next Supply...
  9. whenever $c2 {
  10. say "got: $_";
  11. }
  12. # ...and close the tap on the current one.
  13. last;
  14. }
  15. }
  16. }
  17. $s.tap;
  18. for ^7 {
  19. $c1.emit: "$_ from \$c1";
  20. $c2.emit: "$_ from \$c2";
  21. }

Which will produce:

  1. got: 0 from $c1
  2. got: 1 from $c1
  3. got: 2 from $c1
  4. got: 3 from $c1
  5. listening to something new
  6. got: 3 from $c2
  7. got: 4 from $c2
  8. got: 5 from $c2
  9. got: 6 from $c2

(Note that I removed the sleep 10 because there's no need for it; we aren't introducing any concurrency in this example, so everything runs synchronously.)
Clearly, if there were a dozen Supply s to move between then this approach won't scale so well. So how does migrate work? The key missing piece is that we can obtain the Tap handle when working with whenever , and thus we are able to close it from outside of the body of that whenever . This is exactly how migrate works (copied from the standard library, with comments added):

  1. method migrate(Supply:D:) {
  2. supply {
  3. # The Tap of the Supply we are currently emitting values from
  4. my $current;
  5. # Tap the Supply of Supply that we'll migrate between
  6. whenever self -> \inner {
  7. # Make sure we produce a sensible error
  8. X::Supply::Migrate::Needs.new.throw
  9. unless inner ~~ Supply;
  10. # Close the tap on whatever we are currently tapping
  11. $current.close if $current;
  12. # Tap the new thing and store the Tap handle
  13. $current = do whenever inner -> \value {
  14. emit(value);
  15. }
  16. }
  17. }
  18. }

In short: you don't change the target of the whenever , but rather start a new whenever and terminate the previous one.

展开查看全部

相关问题