为什么功能"有记忆"在REBOL?

时间:2014-09-19 13:54:58

标签: function rebol rebol2

在rebol中,我写了这个非常简单的函数:

make-password: func[Length] [
    chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
    password: ""
    loop Length [append password (pick chars random Length)]
    password
    ]

当我连续多次运行时,事情变得非常混乱:

loop 5 [print make-password 5]

给出(例如)此输出:

  • TWTQW
  • TWTQWWEWRT
  • TWTQWWEWRTQWWTW
  • TWTQWWEWRTQWWTWQTTQQ
  • TWTQWWEWRTQWWTWQTTQQTRRTT

看起来这个函数记住了过去的执行并存储了结果而不是再次使用它!

我没有问这个!

我希望输出类似于以下内容:

  • IPS30
  • DQ6BE
  • E70IH
  • XGHBR
  • 7LMN5

如何实现这一结果?

3 个答案:

答案 0 :(得分:5)

一个好问题。

Rebol代码实际上被认为是一种非常风格化的数据结构。该数据结构“碰巧是可执行的”。但是你需要了解它是如何工作的。

例如,来自@ WiseGenius的建议:

make-password: func[Length] [
    chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
    password: copy ""
    loop Length [append password (pick chars random Length)]
    password
]

查看包含append password...的块。该块在那里“成像”;引擎盖下的真实情况是:

chars: **pointer to string! 0xSSSSSSS1**
password: copy **pointer to string! 0xSSSSSSS2**
loop Length **pointer to block! 0xBBBBBBBB**
password

所有系列在解释器加载时都以这种方式工作。字符串,块,二进制文件,路径,parens等等。鉴于它是“乌龟一直向下”,如果你遵循该指针,块0xBBBBBBBB在内部:

append password **pointer to paren! 0xPPPPPPPP**

这样做的一个结果是可以在多个地方引用一系列(因此“成像”):

>> inner: [a]

>> outer: reduce [inner inner]
[[a] [a]]

>> append inner 'b

>> probe outer
[[a b] [a b]]

这可能会让新手感到困惑,但是一旦了解了数据结构,就会开始知道何时使用COPY。

所以你已经注意到了这个函数的一个有趣含义。考虑一下这个程序:

foo: func [] [
    data: []
    append data 'something
]

source foo

foo
foo

source foo

这产生了可能令人惊讶的结果:

foo: func [][
    data: [] 
    append data 'something
]

foo: func [][
    data: [something something] 
    append data 'something
]

我们多次调用foo,似乎函数的源代码正在改变。从某种意义上说,它是自修改代码。

如果这困扰你,R3-Alpha中有工具可以攻击它。您可以使用PROTECT保护函数体不被修改,甚至可以创建自己的替代方法,如FUNC和FUNCTION,它们将为您完成。 (PFUNC?PFUNCTION?)在Rebol版本3中,您可以写:

pfunc: func [spec [block!] body [block!]] [
    make function! protect/deep copy/deep reduce [spec body]
]

foo: pfunc [] [
    data: []
    append data 'something
]

foo

当你跑步时,你得到:

*** ERROR
** Script error: protected value or series - cannot modify
** Where: append foo try do either either either -apply-
** Near: append data 'something

因此迫使你复制系列。它还指出FUNC只是一个功能!本身,功能也是如此。你可以制作自己的发电机。

这可能会打破你的大脑,你可能会尖叫说“这不是任何理智的软件编写方式”。或许你会说“我的上帝,它充满了星星。”反应可能有所不同。但这对于为系统提供动力并赋予其灵活性的“技巧”来说是相当基础的。

(注意:Ren-C branch of Rebol3基本上已经使得函数体 - 和一般的源序列 - 在默认情况下被锁定。如果一个人想要一个函数中的静态变量,你可以说 foo: func [x <static> accum (copy "")] [append accum x | return accum] ,该功能将在accum跨越呼叫累积状态。)

我还建议密切关注每次运行中实际发生的事情。在运行foo函数之前,数据没有值。每次执行函数时,会发生什么,评估者看到SET-WORD!后跟一个系列值,它执行对变量的赋值。

data: **pointer to block! 0xBBBBBBBB**

在该分配之后,您将对该块存在两个引用。一个是它存在于在LOAD时间建立的代码结构中,在该函数运行之前。第二个引用是存储在数据变量中的引用。通过这个第二个参考,你正在修改这个系列。

请注意,每次运行该功能时都会重新分配数据。但是一遍又一遍地重新分配给相同的值......那个原始的块指针!这就是为什么你必须COPY,如果你想在每次运行中都有一个新的块。

掌握评估者规则中的基本简单性是令人头晕目眩的一部分。这就是简单化打扮成语言的方式(在某种程度上你可以根据自己的方式扭曲)。例如,没有“多项任务”:

a: b: c: 10

这只是评估员将 a:作为SET-WORD!符号并说“好吧,让我们将其绑定上下文中的变量 a 与下一个完整表达式生成的任何内容相关联。” b:也是如此。 c:执行相同操作但由于整数值为10而命中终端...然后计算为10.因此它看起来像多次分配。

所以请记住,系列文字的原始实例是挂在已加载源中的实例。如果评估者有办法做这种SET-WORD!或SET赋值,它将借用指向源中该文字的指针来戳入变量。这是一个可变的参考。您(或您设计的抽象)可以通过PROTECT或PROTECT / DEEP使其不可变,并且您可以使其不是COPY或COPY / DEEP的参考。


相关说明

有些人认为你永远不应该写复制[] ...因为(a)你可能养成忘记写COPY的习惯,并且(b)你正在制作一个未使用过的系列每次你这样做。那个“空白系列模板”被分配,必须由垃圾收集器扫描,没有人真正接触它。

如果你写 make block! 10 (或者你想要预先分配块的大小)你可以避免这个问题,保存一个系列,并提供一个大小提示。

答案 1 :(得分:2)

默认情况下,此表示法不会将字符串""的值复制到password。相反,它将password设置为指向位于函数体块中的字符串。因此,当您在append上执行password时,您实际上会附加到它指向的字符串,该字符串位于函数的主体块中。你实际上正在改变函数体部分的一部分。要查看正在发生的情况,您可以使用??检查您的功能,以便在每次使用时查看其中发生的情况:

make-password: func[Length] [
    chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
    password: ""
    loop Length [append password (pick chars random Length)]
    password
]

loop 5 [
    print make-password 5
    ?? make-password
]

这应该给你类似的东西:

TWTQW
make-password: func [Length][
    chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
    password: "TWTQW"
    loop Length [append password (pick chars random Length)]
    password
]
TWTQWWEWRT
make-password: func [Length][
    chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
    password: "TWTQWWEWRT"
    loop Length [append password (pick chars random Length)]
    password
]
TWTQWWEWRTQWWTW
make-password: func [Length][
    chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
    password: "TWTQWWEWRTQWWTW"
    loop Length [append password (pick chars random Length)]
    password
]
TWTQWWEWRTQWWTWQTTQQ
make-password: func [Length][
    chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
    password: "TWTQWWEWRTQWWTWQTTQQ"
    loop Length [append password (pick chars random Length)]
    password
]
TWTQWWEWRTQWWTWQTTQQTRRTT
make-password: func [Length][
    chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
    password: "TWTQWWEWRTQWWTWQTTQQTRRTT"
    loop Length [append password (pick chars random Length)]
    password
]

要将字符串复制到password而不是指向它,请尝试以下方法:

make-password: func[Length] [
    chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
    password: copy ""
    loop Length [append password (pick chars random Length)]
    password
]

答案 2 :(得分:2)

没有足够的声誉评论HostileFork的答案,我这样反应。关于你的相关注释&#34;,它指出了我从未意识到的事情。

&#34;有些人认为&#34;暗示你不在其中,但是你让我觉得我最好写 str:make string! 0 blk:制作块! 0 从现在开始,不仅仅是在功能范围内。尺寸提示一直困扰着我。如果您不知道最终的幅度,是否有任何建议可供选择? (当然不低于你的最低期望,也不超过最高期望。)