Car*_*orc 6 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]
给(例如)此输出:
看起来这个函数记住了过去的执行并存储了结果而不是再次使用它!
我没问过这个!
我希望输出类似于以下内容:
我怎样才能达到这个效果?
一个好问题.
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只是一个功能!本身,功能也是如此.你可以制作自己的发电机.
这可能会打破你的大脑,你可能会尖叫说"这不是任何理智的软件编写方式".或许你会说"我的上帝,它充满了星星." 反应可能有所不同.但对于为系统提供动力并赋予其灵活性的"技巧"来说,这是相当基础的.
(注意:Rebol3的Ren-C分支从根本上使得函数体 - 和一般的源序列 - 在默认情况下被锁定.如果想要一个函数中的静态变量,你可以说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
那只是评估者打出一个:作为SET-WORD!符号并说"好吧,让我们将其绑定上下文中的变量a与下一个完整表达式产生的任何内容相关联." b:做同样的事. c:做同样但是因为整数值10 ...而命中一个终端然后也计算为10.所以看起来像多次赋值.
所以请记住,系列文字的原始实例是挂在已加载源中的实例.如果评估者有办法做这种SET-WORD!或SET赋值,它将借用指向源中该文字的指针来戳入变量.这是一个可变的参考.您(或您设计的抽象)可以通过PROTECT或PROTECT/DEEP使其不可变,并且您可以使其不是COPY或COPY/DEEP的参考.
相关说明
有些人认为你永远不应该写副本[] ...因为(a)你可能养成忘记写COPY的习惯,(b)你每次做一个未使用的系列.那个"空白系列模板"被分配,必须由垃圾收集器扫描,没有人真正接触它.
如果你写make块!10(或者你想要预先分配块的任何大小)你可以避免这个问题,保存一个系列,并提供一个大小提示.