Rebol 中用于列表理解的“编译器”

noe*_*ein 0 dsl list-comprehension rebol red

我想编译这个列表理解:

>> 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]]
Run Code Online (Sandbox Code Playgroud)

在:

collect [
   foreach x [1 2 3] [
       foreach y [4 5 6] [
           keep/only reduce [x y]]]]
Run Code Online (Sandbox Code Playgroud)

或者:

>> 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]...
Run Code Online (Sandbox Code Playgroud)

在:

collect [
    foreach x range [1 5] [
        foreach y range reduce [1 x] [
            if x + y > 4 [keep/only reduce [x y]]]]]
Run Code Online (Sandbox Code Playgroud)

或者:

>> 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]
Run Code Online (Sandbox Code Playgroud)

在:

collect [
    foreach x range [1 5] [
        foreach y range reduce [1 x] [
           if x + y > 4 [keep reduce [x y]]]]] 
Run Code Online (Sandbox Code Playgroud)

我在 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
]
Run Code Online (Sandbox Code Playgroud)

程序没有流动,它充满了变量、ifappend(我在parse方面遇到了困难;在这种情况下compose是有限的)。
有没有更“rebo​​lish”的方式(在 rebol2/rebol3/red/ren-c 中)来解决这个问题?

更新:我对“编译器”的实现只是我打算做什么的一个指示。我不是要求更正我的程序(我什至无法报告它),而是要求更好地使用解析并更清晰地构建代码的解决方案。

921*_*214 7

列表理解是构建方言的一个很好的练习:它的规模适中,具有明确的目的,并且通常用作有抱负的年轻 Grasshopper 的代码型——没有单一的“正确”方法可以做到这一点,但是很多更好或更坏的解决方案。

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

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

假设您只是涉足元编程或学习 Red 没有任何特定问题,并考虑到我的初步评论和您随后的编辑,这里是我的 2¢:

从语法开始

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

set-builder: [expression '| some generator predicate]
expression:  [to '|]
generator:   [word! 'in to [generator | predicate | end]]
predicate:   ['if to end]
Run Code Online (Sandbox Code Playgroud)

弄清楚要发出什么

你已经知道了:对于 set-builder 符号中的每个生成器,我们需要一个额外的foreach; 内在的身体foreach应该有形式<predicate> [<keep> <expression>]

定义一个接口

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

list: function [spec [block!] /only][...]
Run Code Online (Sandbox Code Playgroud)

将点连接

继续婴儿的步骤并开始简单:

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

第1步

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

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

set-builder: [collect [expression '| collect some generator predicate]]
Run Code Online (Sandbox Code Playgroud)
  1. expression 很简单:

    expression: [keep to '|]
    
    Run Code Online (Sandbox Code Playgroud)
  2. 那么作为predicate,但我们需要keepif也:

    predicate: [ahead 'if keep to end]
    
    Run Code Online (Sandbox Code Playgroud)
  3. generator更棘手,有两个原因:

    1. 有一种叫做部分匹配的东西。我们不能只写:

      generator: [keep word! 'in keep to [generator | predicate | end]]
      
      Run Code Online (Sandbox Code Playgroud)

      generatororpredicate内部匹配时to,会keep因为word!orto 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] ...
      
      Run Code Online (Sandbox Code Playgroud)

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

    generator: [keep word! 'in keep copy range to [word! 'in | 'if | end]]
    
    Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

第2步

首先,我们组成inner的body块foreach

body: [<clause> [<keep> <expression>]]
Run Code Online (Sandbox Code Playgroud)

这将导致:

body: compose/deep [(any [clause 'do]) [(pick [keep/only keep] only) (expression)]]
Run Code Online (Sandbox Code Playgroud)

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

让我们弄清楚后续的每一层是foreach怎样的:

layer: [foreach <word> <range> <body>]
Run Code Online (Sandbox Code Playgroud)

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

layer: [foreach (word) (range) [(body)]]
Run Code Online (Sandbox Code Playgroud)

最后一件事:我们从上到下提取数据,但我们需要以相反的方式累积数据foreach,从body. 所以我们需要reverse范围块:

foreach [range word] reverse ranges [...]
Run Code Online (Sandbox Code Playgroud)

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

collect foreach [range word] reverse ranges [body: compose/deep layer]
Run Code Online (Sandbox Code Playgroud)

整件事是:

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]
]
Run Code Online (Sandbox Code Playgroud)

例子:

>> 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]]
Run Code Online (Sandbox Code Playgroud)

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

在任何情况下,如果您发现自己在 Parse 中苦苦挣扎,那么您可能想要浏览并浏览管道中的参考文档parseGitter 室,您可以在其中寻求帮助。

一旦您重构了代码并对其感到满意,请在 Red社区聊天中分享快乐并获得一些反馈。在那之前,保重!