【问题标题】:What is the difference in contexts in nested and non-nested maps in Perl 6?Perl 6 中嵌套和非嵌套映射的上下文有什么区别?
【发布时间】:2017-01-17 11:59:48
【问题描述】:

我有这段代码。第一个,非嵌套的 map 输出一些东西,而嵌套的则没有。我想我明白为什么第二个不起作用。这是一个惰性序列,Perl 6 正在收集结果。没关系。但是第一个(非嵌套)map 不是以同样的方式懒惰吗?如果我不对地图的结果做任何事情,它如何输出任何东西?也就是第一个怎么懒?是否会自动获得一个接收器上下文,我必须明确地向嵌套的上下文提供 sink (或其他东西)?不知何故,我认为 Perl 6 应该能够为我解决这个问题。

my @array = (1, 2), (3, 4), ('a', 'b');

say "---On its own:";
my @item = 1, 2, 3;
@item.map: {
    say $_;
    };

say "---Inside another map:";
@array.map: {
    my @item = 1, 2, 3;
    @item.map: {
        say $_;
        }
    };

这是输出:

---On its own:
1
2
3
---Inside another map:

这与问题How can I use "map" inside a "for" loop in Perl 6?有关。那个说要做什么,但我问的更多的是一个为什么的问题。

解决方案是在内部添加eagersink 或赋值map

say "---eager:";
@array.map: {
    my @item = 1, 2, 3;
    eager @item.map: {
        say $_;
        }
    };

say "---sink:";
@array.map: {
    my @item = 1, 2, 3;
    sink @item.map: {
        say $_;
        }
    };

say "---assignment:";
@array.map: {
    my @item = 1, 2, 3;
    @ = @item.map: {
        say $_;
        }
    };

【问题讨论】:

  • 不知何故,我认为 Perl 6 应该能够为我解决这个问题。 返回 Seq 是一个有效的用例。由于编译器无法读心,您更喜欢哪种常规语义?使接收器上下文递归,在接收器上下文中迭代序列的所有子序列?对我来说,这看起来像是一罐可能应该保持关闭的蠕虫
  • 我更希望有一个类似地图的东西,它会返回一个列表。它是一个序列而不是有序列表的想法对我来说似乎很奇怪。
  • 回想起来,我也花了一段时间才适应了序列:理论上,它们是更好的原语(例如,您可以轻松地将序列制作成列表,但反过来不行),但是在实践中存在一些摩擦。不过,这个特殊问题不是关于序列与列表,而是懒惰......
  • 所以您希望map { $_+1 }, 1..*[\+] 1..* 返回一个列表?如果你只需要 Seq 的前几个元素,它只会产生前几个,而不是浪费一堆时间。 say [\+](1,3...*)[^20] Seqs 也可以在迭代中使用它们后丢弃这些值(如果你不做任何事情来缓存它们)。曾经有一段时间,List 会同时完成这两项工作,即使对于实施者来说,也更难推理。
  • 我真的什么都不想要。初学者在使用某些东西之前必须知道多少。在这里他们必须明白一点,所以他们会犯这种错误。

标签: lazy-evaluation raku


【解决方案1】:

每个程序文件、块、子程序等的内容是一个“语句列表”,由分号分隔的1语句组成。考虑到这一点:

  1. 得到沉没的语句:

    • 语句列表中除最后一条语句之外的所有语句。
    • 语句列表级别3 的任何循环语句(forwhile2),即使它是最后一个。李>
    • 程序或模块文件的顶级语句列表中的任何语句,即使它是最后一个。4
  2. 得到返回而不是沉没的语句:

    • 声明列表中的最终声明,上述情况除外。

下沉的力量渴望评估,回归没有。

示例

在您的情况下,第一个 map 语句位于语句列表的中间,因此它沉没

但嵌套的map 语句是其语句列表的最后一条语句,因此其结果以尚未迭代的Seq 的形式返回

它的父map语句也是最终语句,但是它在程序文件的顶级语句列表中,所以它沉没,导致它急切地迭代包含的序列三个Seq 值。 (在内部 map 之前插入 say 语句,以查看此内容。)
但是,这三个内部 Seq 值中的每一个都不会下沉或迭代。

来自设计文档

更详细的,来自Synopsis 04, line 664

在任何语句序列中,只返回最终语句的值,因此所有之前的语句都在接收器上下文中进行评估,这会自动急切地强制评估副作用。 (副作用是首先执行此类语句的唯一原因,事实上,如果您在接收器上下文中执行“无用”的操作,Perl 会警告您。)接收器上下文中的循环不仅急切地评估自己,但可以优化循环中任何值的产生。

语句列表的最后一条语句不是接收器上下文,并且可以返回任何值,包括惰性列表。然而,为了支持命令式程序员(我们绝大多数人,事实证明)的期望,作为语句列表的最后一条语句找到的任何显式循环都会自动强制使用接收器语义,以便循环在从块返回之前执行完毕。

这种强制接收器上下文仅应用于语句列表级别的循环,即编译单元的顶层,或直接在块内。将单个语句或半列表解析为参数的构造被假定需要该语句的结果,因此即使该语句是循环,此类构造仍保持惰性。


1) 当右大括号 } 作为行的最后一个正确标记出现时,如
my @a = @b.map: { $_ + 1 } # whitespace/comment doesn't count
它也结束当前的语句,但否则需要一个分号来分隔语句。

2) map 不算,因为它是一个函数而不是循环关键字。

3) 表示当循环语句出现在不同于直接出现在语句列表中的位置时,例如
lazy for ^10 { .say } # as argument to a keyword expecting a single statement
(for ^10 { .say }) # inside an expression
那么默认情况下它不会沉没。这就是概要引文的最后一段想要表达的意思。

更新:Rakudo 中的情况似乎并非如此,但may be a bug

4) 概要中没有提到这条规则,但它在 Rakudo 中是这样运作的,我很确定它是故意的。

【讨论】:

  • 奇怪的是,没有警告。
【解决方案2】:

.map 基本上返回一个 .Seq。发生的情况是内部映射返回一个 Seq 到外部映射,但由于该映射的结果是下沉的,它们会在没有被迭代的情况下消失。

如果说外层映射,就会拉取内层映射的结果,你会看到内层映射返回的.seq结果:

my @array = (1, 2), (3, 4), ('a', 'b');
say "---Inside another map:";
say @array.map: {
    my @item = 1, 2, 3;
    @item.map: {
        say $_;
        }
    }
---Inside another map:
1
2
3
1
2
3
1
2
3
((True True True) (True True True) (True True True))

希望这是有道理的:-)

另一种解决方案是将特定的返回值添加到外部映射。然后内部地图会下沉,因此会迭代,如下所示:

my @array = (1, 2), (3, 4), ('a', 'b');
say "---Inside another map:";
say @array.map: {
    my @item = 1, 2, 3;
    @item.map: {
        say $_;
        }
    42   # make sure ^^ map is sunk
    }
---Inside another map:
1
2
3
1
2
3
1
2
3

【讨论】:

  • @briandfoy:你的意思是让垃圾收集器在释放一个既没有沉没也没有迭代的Seq 时发出警告?
  • 就解决方案而言,最好的建议是使用像for 这样的循环关键字(而不是像map 这样的函数)进行命令式编程,这样您就不会遇到这样的意外。跨度>
  • 我不在乎是谁发出警告。甚至可能是编译时警告。
  • @briandfoy:在一般情况下,我认为编译器不能足够聪明地在编译时解决这个问题(例如,方法调用是动态解析的,所以它甚至可能还不知道.map 返回 Seq)。另外,请记住,您不希望仅仅因为从块中返回尚未迭代的Seq 而发出警告,因为当您以更“函数式编程”风格进行编码时,这通常是预期的行为!您必须抓住可能是无意的情况。我不确定我的“垃圾收集器”想法是否会在不造成误报的情况下做到这一点。
  • @briandfoy:通常,Perl 6 或多或少支持带有forwhile、数组变量、赋值等的无意外“命令式编程”。其他功能,如mapX, Z`、绑定等更适合函数式编程,需要了解语言如何处理惰性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-20
  • 2014-04-30
  • 1970-01-01
相关资源
最近更新 更多