'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
]
程序没有流,它充满了if和append[=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][...]
连接点
从简单的步骤开始:
- 用
set-builder
解析spec
并提取相关部分进行进一步处理。
- 将提取的部分组合在一起。
步骤 1
我们需要相应地修改我们的语法:在需要的地方添加 collect
/ keep
并应对边缘情况。
我们需要提取 3 个部分:表达式、生成器和谓词。我们可以通过添加额外的 collect
:
将生成器组合在一起
set-builder: [collect [expression '| collect some generator predicate]]
expression
很简单:
expression: [keep to '|]
所以如predicate
,但我们还需要keep
if
:
predicate: [ahead 'if keep to end]
但是 generator
比较棘手,原因有二:
有个东西叫部分匹配。我们不能只写:
generator: [keep word! 'in keep to [generator | predicate | end]]
当generator
或predicate
在to
内匹配时,会递归keep
额外的数据,因为word!
或to end
匹配,弄乱了提取的块。
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
包含范围,每个范围又包含一个要迭代的单词和一个范围本身。 clause
是 block!
(如果它存在于 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 中分享快乐并获得一些反馈。在那之前,保重!
我想编译这个列表理解:
>> 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
]
程序没有流,它充满了if和append[=44=的变量](我在使用 parse 时遇到了困难;compose,在这种情况下,是有限的)。
是否有更多 "rebolish" 方法(在 rebol2/rebol3/red/ren-c 中)来解决这个问题?
更新:我对 'compiler' 的实施只是表明我打算做什么。我不是要求更正我的程序(我什至无法报告),而是要求更好地使用 parse 并更清晰地构建代码的解决方案。
列表理解是方言建设中的一个很好的练习:它适度小,有明确的目的,一般来说,作为有抱负的年轻 Grasshopper 的代码套路 — 没有单一的 "right"方法,但有很多更好或更差的解决方案。
"Rebolish" 方法是保持务实,从用例开始,让问题域引导你——也许你正在解决 Euler 项目并且需要一个集合论库,也许你想要什么是类似LINQ的数据库查询,也许只是为了学习和重新发明轮子的乐趣,谁知道呢?
在考虑时,您可能会意识到您实际上不需要列表理解,这很好!最精简的代码是从未编写过的代码,而最聪明的解决方案是通过用更简单的术语重新定义问题,将问题扼杀在萌芽状态。
假设您只是涉足元编程或学习 Red,没有任何具体问题,并考虑到我最初的
从语法开始
列表理解作为一种句法结构,具有明确定义的形式。查阅相应的 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][...]
连接点
从简单的步骤开始:
- 用
set-builder
解析spec
并提取相关部分进行进一步处理。 - 将提取的部分组合在一起。
步骤 1
我们需要相应地修改我们的语法:在需要的地方添加 collect
/ keep
并应对边缘情况。
我们需要提取 3 个部分:表达式、生成器和谓词。我们可以通过添加额外的 collect
:
set-builder: [collect [expression '| collect some generator predicate]]
expression
很简单:expression: [keep to '|]
所以如
predicate
,但我们还需要keep
if
:predicate: [ahead 'if keep to end]
但是
generator
比较棘手,原因有二:有个东西叫部分匹配。我们不能只写:
generator: [keep word! 'in keep to [generator | predicate | end]]
当
generator
或predicate
在to
内匹配时,会递归keep
额外的数据,因为word!
或to end
匹配,弄乱了提取的块。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
包含范围,每个范围又包含一个要迭代的单词和一个范围本身。 clause
是 block!
(如果它存在于 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 中分享快乐并获得一些反馈。在那之前,保重!