在 rebol 中,我写了这个非常简单的函数:
make-password: func[Length] [
chars: "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"
password: ""
loop Length [append password (pick chars random Length)]
password
]
当我连续多次运行时,事情变得非常混乱:
loop 5 [print make-password 5]
给出(例如(此输出:
- 台特昆
- TWTQWWEWRT
- TWTQWWEWRTQWWTW
- TWTQWWEWRTQWWTWQTTQQ
- TWTQWWEWRTQWWTWQTTQQTRRTT
看起来该函数记住了过去的执行并存储了结果,然后再次使用它!
我没有问这个!
我希望输出类似于以下内容:
- IPS30
- DQ6BE
- E70IH
- XGHBR
- 7LMN5
我怎样才能达到这个结果?
一个好问题。
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
所有系列在由解释器加载时都是这样工作的。 字符串、块、二进制文件、路径、参数等。 鉴于它是"一路向下",如果你遵循该指针,块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? 功能? 在 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只是一个功能!本身,功能也是如此。 您可以制作自己的发电机。
这可能会打破你的大脑,你可能会尖叫着说"这不是编写软件的任何理智方式"。 或者你会说"我的上帝,这里到处都是星星。 反应可能会有所不同。 但它对于为系统提供动力并赋予其疯狂灵活性的"技巧"来说是相当基本的。
(注意:Rebol3 的 Ren-C 分支从根本上做到了函数体——以及一般的源码系列——默认被锁定。 如果想要函数中的静态变量,你可以说foo: func [x <static> accum (copy "")] [append accum x | return accum]
,函数将在调用中累积accum
的状态。
我还建议密切关注每次运行中实际发生的事情。 在运行 foo
函数之前,数据没有价值。 每次我们执行函数并且评估器看到一个 SET-WORD 时,都会发生的情况!后跟一个系列值,它执行对变量的赋值。
data: **pointer to block! 0xBBBBBBBB**
完成该分配后,您将有两个对现有块的引用。 一个是它存在于 LOAD 时建立的代码结构中,在函数运行之前。 第二个引用是存储在数据变量中的引用。 正是通过第二个引用,您正在修改此系列。
请注意,每次运行函数时都会重新分配数据。 但是一遍又一遍地重新分配给相同的值...那个原始的块指针! 这就是为什么如果你想在每次运行时都有一个新的块,你必须复制。
掌握评估器规则中潜在的简单性是令人眼花缭乱的趣味性的一部分。 这就是简单性被打扮成语言的方式(在某种程度上,你可以扭曲到你自己的手段(。 例如,没有"多重分配":
a: b: c: 10
这只是求值器点击 a: 作为 SET-WORD! 符号并说"好的,让我们将变量 a 在其绑定上下文中与下一个完整表达式产生的任何内容相关联。 乙:做同样的事情。 C:做同样的事情,但由于整数值 10 而命中终端...然后计算结果也为 10。 所以它看起来像多重分配。
因此,请记住,系列文本的原始实例是悬挂在加载的源中的实例。 如果评估者有时间做这种设置字!或 SET 赋值,它将借用指向源中该文本的指针来戳到变量中。 这是一个可变的引用。 你(或你设计的抽象(可以用 PROTECT 或 PROTECT/DEEP 让它不可变,你可以用 COPY 或 COPY/DEEP 让它不作为引用。
相关说明
有些人认为你永远不应该写复制[]...因为(a(你可能会养成忘记写COPY的习惯,(b(你每次做的时候都在做一个未使用的系列。 分配的"空白系列模板"必须由垃圾收集器扫描,没有人真正接触过它。
如果你写 make block!10(或你想要预分配块的任何大小(,你可以避免这个问题,保存一个系列,并提供一个大小提示。
,此表示法不会将字符串的值""
复制到password
。相反,它将password
设置为指向位于函数正文块中的字符串。因此,当您对password
执行append
时,您实际上是附加到它指向的字符串,该字符串位于函数的正文块中。您实际上是在更改函数正文块的一部分。若要查看发生了什么,可以使用??
检查函数,以观察每次使用它时会发生什么:
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
]
没有足够的声誉来评论 HostileFork 的答案,我以这种方式做出反应。这是关于你的"相关笔记",它向我指出了一些我从未意识到的事情。
"有些人争论"表明你不在其中,但尽管如此,你让我觉得我最好写 str:make string! 0 和 blk:制作块! 从现在开始,不仅在函数内。尺寸提示一直让我感到困惑。如果您不知道最终的量级,是否有任何建议可以在这里选择什么?(当然不低于您的最低期望,也不高于最大值。