Rebol:块词的动态绑定

Rebol: Dynamic binding of block words

在 Rebol 中,像 foreach 这样的词允许 "block parametrization" 超过给定的词和一系列,例如 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
    ]
]

我发现这种方法很笨拙(可能确实如此),所以我想知道如何更优雅地解决这个问题。无论如何,在想出我的装置后,我有两个问题:

  1. 在 bind-var 中,我不得不在 bind [(block)] (:qw) 中做一些包装,因为 (block) 会 "dissolve"。为什么?

  2. 因为 2 的 (?),绑定操作是在一个新块(由 [(block)] 表达式创建)上执行的,而不是传递给 my-foreach 的原始块,具有单独的绑定,所以我必须对其进行操作。我错误地添加了 [(block)],它仍然有效。但为什么?

好问题。 :-) 在 Rebol2 和 R3-Alpha 中编写您自己的自定义循环结构(现在,历史与 Red 重演)有许多未解决的问题。 Rebol3 开发人员和 considered blocking bugs.

都知道这些类型的问题

Ren-C was started was to address such concerns. Progress has been made in several areas 的原因,虽然在撰写本文时仍然存在许多突出的设计问题。但是,我将尝试仅根据历史假设来回答您的问题。)

In bind-var, I had to do some wrapping in bind [(block)] (:qw) because (block) would "dissolve". Why?

默认情况下 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)] 不会被替换。

By mistake, I added [(block)] and it still works. But why?

如果您进行类似 my-foreach x [1] [print x probe bind? 'x] 的测试,bind? 的输出将显示它已绑定到 "global" 用户上下文。

从根本上说,您没有任何 MAKE OBJECT!或使用创建一个新的上下文来绑定正文。因此,您在这里 可能 可以做的就是剥离代码中 x 的任何现有绑定,并确保它们进入用户上下文。

但最初你确实有一个用途,你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)]
    ]
]

the bind operation is performed on a new block (created by the [(block)] expression), not the original one passed to my-foreach, with separate bindings

让我们调整您的代码,删除深入走动的 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 has no value"(假设您没有 x 的一些潜在全局定义,它会获取,这只会打印相同的潜在价值 3 倍)。

但为了让您对您提出的问题感到非常抱歉:-),我会提到 my-foreach x [1 2 3] [loop 1 [print x]] 实际上 有效 。那是因为虽然您说过去绑定不应该影响新块是正确的,但此 COMPOSE 仅创建 one 新块!。最顶层是新的,源 material 中引用的任何 "deeper" 嵌入块将是原始 material:

的别名
>> 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 中有很多新技术会影响你的问题),你仍然可能想要做它只有一次并在迭代中重复使用它。