'compiler' 用于 Rebol 中的列表理解

A 'compiler' for list comprehension in Rebol

我想编译这个列表理解:

>> lc [reduce [x y] | x in [1 2 3] y in [4 5 6]]
== [[1 4] [1 5] [1 6] [2 4] [2 5] [2 6] [3 4] [3 5] [3 6]]

在:

collect [
   foreach x [1 2 3] [
       foreach y [4 5 6] [
           keep/only reduce [x y]]]]

或:

>> lc [reduce [x y] | x in range [1 5] y in range reduce[1 x] if x + y > 4]
== [[3 2] [3 3] [4 1] [4 2] [4 3] [4 4] [5 1] [5 2] [5 3] [5 4] [5 5]...

在:

collect [
    foreach x range [1 5] [
        foreach y range reduce [1 x] [
            if x + y > 4 [keep/only reduce [x y]]]]]

或:

>> lc/flat [reduce [x y] | x in range [1 5] y in range reduce [1 x] if x + y > 4]
== [3 2 3 3 4 1 4 2 4 3 4 4 5 1 5 2 5 3 5 4 5 5]

在:

collect [
    foreach x range [1 5] [
        foreach y range reduce [1 x] [
           if x + y > 4 [keep reduce [x y]]]]] 

我在 Red 中的丑陋实现是:

fx: func [code] [func [x] code]
lc: function [list-comp /flat] [ ; list-comp = [code | generators [opt if test]]
    flat: any [flat false]
    var: none
    part-gen: part-if: rest: code: []
    rule-var: [set var word! 'in]
    list: copy []
    generator+if: [copy part-gen to 'if copy part-if to end]
    generator: [copy part-gen to end]
    emit: fx [append/only list x]
    parse list-comp [
        copy code to '| skip [
            generator+if 
        | 
            generator   ]
        ]
    parse part-gen [
        some [
            rule-var (emit var) copy rest to [rule-var | end ] (emit rest)
            ]
        ]
    option: either flat [copy [keep]] [copy [keep/only]]
    code: append option code
    if part-if <> [] [code: append/only part-if code]
    foreach [l v] reverse list [code: compose [foreach (v) (l) (reduce [code])]]
    collect code
]

; from hof.r
range: func [
    {Makes a block containing a range of ord! values.
    Format: .. [1 5]   == [1 2 3 4 5]
            .. [1 3 6] == [1 2 5]
            .. [2 2 6] == [2 2 2 2 2 2]
    }
    xs [block!] {either [start end] or [start next end]}
    /local range x1 x2 delta result [block!]
][
    range: reduce xs
    x1: range/1
    either range/3 [
        x2: range/3
        delta: (range/2 - x1)
    ][
        x2: range/2
        delta: 1
    ]

    ;result: make block! (x2 - x1) / delta
    result: copy []
    either delta <> 0 [
        result: reduce [x1]
        loop x2 - x1 [
            append result delta + last result
        ]
    ][
        loop absolute x2 [
            insert tail result x1
        ]
    ]
    result
]

程序没有,它充满了ifappend[=44=的变量](我在使用 parse 时遇到了困难;compose,在这种情况下,是有限的)。
是否有更多 "rebolish" 方法(在 rebol2/rebol3/red/ren-c 中)来解决这个问题?

更新:我对 'compiler' 的实施只是表明我打算做什么。我不是要求更正我的程序(我什至无法报告),而是要求更好地使用 parse 并更清晰地构建代码的解决方案。

列表理解是方言建设中的一个很好的练习:它适度小,有明确的目的,一般来说,作为有抱负的年轻 Grasshopper 的代码套路 — 没有单一的 "right"方法,但有很多更好或更差的解决方案。

"Rebolish" 方法是保持务实,从用例开始,让问题域引导你——也许你正在解决 Euler 项目并且需要一个集合论库,也许你想要什么是类似LINQ的数据库查询,也许只是为了学习和重新发明轮子的乐趣,谁知道呢?

在考虑时,您可能会意识到您实际上不需要列表理解,这很好!最精简的代码是从未编写过的代码,而最聪明的解决方案是通过用更简单的术语重新定义问题,将问题扼杀在萌芽状态。

假设您只是涉足元编程或学习 Red,没有任何具体问题,并考虑到我最初的 with your subsequent edit,这是我的 2¢:

从语法开始

列表理解作为一种句法结构,具有明确定义的形式。查阅相应的 wiki 页面可以立即定义基本语法:

set-builder: [expression '| some generator predicate]
expression:  [to '|]
generator:   [word! 'in to [generator | predicate | end]]
predicate:   ['if to end]

弄清楚要发出什么

你已经知道了:对于集合生成器符号中的每个生成器,我们需要一个额外的层 foreach;内部 foreach 的正文应具有 <predicate> [<keep> <expression>].

的形式

定义接口

我会使用 /only 而不是 /flat,因为这是一个众所周知的习语。由于函数体中有很多 set-word!,我将使用 function 构造函数:

list: function [spec [block!] /only][...]

连接点

从简单的步骤开始:

  1. set-builder解析spec并提取相关部分进行进一步处理。
  2. 将提取的部分组合在一起。

步骤 1

我们需要相应地修改我们的语法:在需要的地方添加 collect / keep 并应对边缘情况。

我们需要提取 3 个部分:表达式、生成器和谓词。我们可以通过添加额外的 collect:

将生成器组合在一起
set-builder: [collect [expression '| collect some generator predicate]]
  1. expression 很简单:

    expression: [keep to '|]
    
  2. 所以如predicate,但我们还需要keep if

    predicate: [ahead 'if keep to end]
    
  3. 但是 generator 比较棘手,原因有二:

    1. 有个东西叫部分匹配。我们不能只写:

      generator: [keep word! 'in keep to [generator | predicate | end]]
      

      generatorpredicateto内匹配时,会递归keep额外的数据,因为word!to end匹配,弄乱了提取的块。

    2. keep 根据保留的值的数量表现不同:它按原样保留单个值,但将许多值组合在一起。

      [1 2 3]         -> foreach x [1 2 3] ..., not foreach x 1 2 3 ...
      [range [4 5 6]] -> foreach y range [4 5 6] ...
      

    所以,我们需要的是 (a) 一个规则来检查我们看到的东西确实是一个生成器,而不提取任何东西(word! 'in 应该do) 和 (b)keep 的轻微修改将始终提取 block!keep copy dummy-word。瞧瞧:

    generator: [keep word! 'in keep copy range to [word! 'in | 'if | end]]
    

现在将所有这些混合在一起:

set-builder: [collect [expression '| collect some generator predicate]]
expression:  [keep to '|]
generator:   [keep word! 'in keep copy range to [word! 'in | 'if | end]]
predicate:   [ahead 'if keep to end]

set [expression ranges: clause:] parse spec set-builder

请注意,我在一个块中使用 set-word!s 来颠覆 function 我们的事业。 ranges 包含范围,每个范围又包含一个要迭代的单词和一个范围本身。 clauseblock!(如果它存在于 spec 中)或 none!(如果没有)。

第 2 步

首先,我们组成内部的主体块foreach:

body: [<clause> [<keep> <expression>]]

这导致:

body: compose/deep [(any [clause 'do]) [(pick [keep/only keep] only) (expression)]]

其中涵盖了两个额外的情况:没有谓词(无条件评估)和存在 /only 细化。

让我们看看后续foreach的每一层是怎样的:

layer: [foreach <word> <range> <body>]

<word> 可以按原样使用; <range>可能拼接了; <body>body 或最里面的 layer。由于范围拼接(即从提取的数据中剥离额外的一层[...]),我们不能使用compose/only,所以我们需要将<body>包裹在一个块中并使用compose/deep:

layer: [foreach (word) (range) [(body)]]

最后一件事:我们从上到下提取数据,但我们需要以相反的方式累积数据,将 foreach 层叠在另一个上,从 body 开始。所以我们需要 reverse 范围块:

foreach [range word] reverse ranges [...]

一切就绪!现在只需在顶部拍打 collect 并跟踪 body 以在下一次迭代中结束:

collect foreach [range word] reverse ranges [body: compose/deep layer]

整个事情是:

list: function [
    "List comprehension"
    spec [block!]
    /only
][
    #1
    set-builder: [collect [expression '| collect some generator predicate]]
    expression:  [keep to '|]
    generator:   [keep word! 'in keep copy range to [word! 'in | 'if | end]]
    predicate:   [ahead 'if keep to end]

    set [expression ranges: clause:] parse spec set-builder

    #2
    body:  compose/deep [(any [clause 'do]) [(pick [keep/only keep] only) (expression)]]
    layer: [foreach (word) (range) [(body)]]

    collect foreach [range word] reverse ranges [body: compose/deep layer]
]

示例:

>> list [as-pair x y | x in [1 2 3] y in [4 5 6]]
== [1x4 1x5 1x6 2x4 2x5 2x6 3x4 3x5 3x6]
>> list/only [reduce [x y] | x in range [1 5] y in range reduce [1 x] if x + y > 4]
== [[3 2] [3 3] [4 1] [4 2] [4 3] [4 4] [5 1] [5 2] [5 3] [5 4] [5 5]]

好看吗?如果它能帮助你理解 Red、Parse 和方言,那么我认为它可能是。不好吗?如果是这样,那么您可以从我的错误中吸取教训并做得更好。

无论如何,如果您发现自己在 Parse 上遇到困难,可以在 reference documentation in the pipeline that you might want to skim through and parse Gitter 房间寻求帮助。

重构代码并对其感到满意后,在 Red community chat 中分享快乐并获得一些反馈。在那之前,保重!