在Rebol中,有像 foreach 这样的词允许对给定的单词和系列进行“块参数化”,例如foreach w [1 2 3] [print w]
。由于我发现该语法非常方便(与传递func块相反),我想将它用于我自己在懒惰列表上运行的单词,例如map/stream x s [... x ... ]
。
如何调用这种语法习惯用法?如何正确实施?
我正在搜索文档,但我找不到直接的答案,所以我试图自己实现foreach。基本上,我的实现分为两部分。第一部分是一个函数,它将块中的特定单词绑定到给定值,并生成带有绑定单词的新块。
bind-var: funct [block word value] [
qw: load rejoin ["'" word]
do compose [
set (:qw) value
bind [(block)] (:qw)
[(block)] ; This shouldn't work? see Question 2
]
]
使用它,我按如下方式实现了foreach:
my-foreach: func ['word s block] [
if empty? block [return none]
until [
do bind-var block word first s
s: next s
tail? s
]
]
我发现这种方法非常笨拙(可能是这样),所以我想知道如何更优雅地解决问题。无论如何,在提出我的装置后,我有两个问题:
在bind-var中,我不得不在bind [(block)] (:qw)
中进行一些包装,因为(block)
会“解散”。为什么呢?
因为(?)为2,绑定操作是在一个新块(由[(block)]
表达式创建)上执行的,而不是传递给my-foreach的原始块,具有单独的绑定,所以我必须对此进行操作。错误地,我添加了[(block)]
,它仍然有效。但为什么?
答案 0 :(得分:4)
好问题。 :-)在Rebol2和R3-Alpha中编写自己的自定义循环结构(现在,历史重复为Red)有许多未解决的问题。 Rebol3开发人员和considered blocking bugs已知这些问题。
(启动Ren-C的原因是为了解决这些问题。Progress has been made several areas,但在撰写本文时仍存在许多优秀的设计问题。我会尝试然而,只是在历史假设下回答你的问题。)
在bind-var中,我必须在
bind [(block)] (:qw)
中进行一些包装,因为(block)
会“解散”。为什么呢?
这就是COMPOSE默认工作的方式......而且它通常是首选的行为。如果您不想这样,请使用COMPOSE / ONLY,并且不会拼接块,而是按原样插入。
qw: load rejoin ["'" word]
你可以转换WORD! LIT-WORD!通过to lit-word! word
。您也可以将报价责任转移到样板文件中,例如set quote (word) value
,并完全避免qw
。
避免LOAD通常也是首选,因为它默认情况下会将内容带入用户上下文 - 因此它会丢失原始单词的绑定。执行TO转换将保留原始WORD的绑定!在生成的LIT-WORD中!。
do compose [ set (:qw) value bind [(block)] (:qw) [(block)] ; This shouldn't work? see Question 2 ]
大概你在这里意味着COMPOSE / DEEP,否则这根本不起作用...定期COMPOSE嵌入式PAREN!s 咳嗽,GROUP! s for [(block)]
将不会被替换。
错误地,我添加了
[(block)]
,它仍然有效。但为什么呢?
如果您执行my-foreach x [1] [print x probe bind? 'x]
之类的测试,bind?
的输出将显示它已绑定到“全局”用户上下文。
从根本上说,你没有任何制造对象!或USE创建一个新的上下文绑定身体。因此,潜在在此处所做的一切都将剥离x代码中的任何现有绑定,并确保它们进入用户上下文。
但最初你确实有一个USE,即edited to remove。这更像是在正确的轨道上:
bind-var: func [block word value /local qw] [
qw: load rejoin ["'" word]
do compose/deep [
use [(qw)] [
set (:qw) value
bind [(block)] (:qw)
[(block)] ; This shouldn't work? see Question 2
]
]
]
你是正确的,怀疑你的约束力有些歪斜。但这样做的原因是因为你的BIND只是重做USE本身所做的工作。已经深入使用以确保调整任何单词绑定。所以你可以完全省略绑定:
do compose/deep [
use [(qw)] [
set (:qw) value
[(block)]
]
]
对新块(由
[(block)]
表达式创建)执行绑定操作,而不是传递给my-foreach
的原始块,具有单独的绑定
让我们通过深度使用USE来调整您的代码,以展示您思考的问题。我们将使用一个简单的MAKE OBJECT!代替:
bind-var: func [block word value /local obj qw] [
do compose/deep [
obj: make object! [(to-set-word word) none]
qw: bind (to-lit-word word) obj
set :qw value
bind [(block)] :qw
[(block)] ; This shouldn't work? see Question 2
]
]
现在,如果你尝试my-foreach x [1 2 3] [print x]
,你会得到你所怀疑的......“x没有价值”(假设你没有一些潜在的全局定义x它会拾取,这只会打印出来相同的潜伏值3倍)。
但为了让你充分抱歉,你问:-),我会提到my-foreach x [1 2 3] [loop 1 [print x]]
实际有效。那是因为虽然你说过去的绑定不应该影响新的块是正确的,但是这个COMPOSE只创建了一个新的BLOCK!最顶层是新的,源材料中引用的任何“更深”的嵌入块将是原始材质的别名:
>> original: [outer [inner]]
== [outer [inner]]
>> composed: compose [<a> (original) <b>]
== [<a> outer [inner] <b>]
>> append original/2 "mutation"
== [inner "mutation"]
>> composed
== [<a> outer [inner "mutation"] <b>]
因此,如果您对撰写的结果进行变异BIND,则会严重影响您的部分。
until [ do bind-var block word first s s: next s tail? s ]
关于效率的一般说明,您在循环的每次迭代中运行COMPOSE和BIND操作。无论对这些问题有多么有创意的新解决方案(Ren-C中有很多新技术会影响你的问题),你仍然可能只想做一次并在迭代中重复使用它。