为什么功能“有记忆”在REBOL? [英] Why do function "have memory" in REBOL?

查看:124
本文介绍了为什么功能“有记忆”在REBOL?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

$ p $ make $ password make func [Length] [
字符:QWERTYUIOPASDFGHJKLZXCVBNM1234567890
密码:
循环长度[追加密码(随机选择字符长度)]
密码
]


当我连续多次运行这些东西时,会变得非常混乱:

  loop 5 [print make-password 5] 

给出(例如)this输出:


  • TWTQW

  • TWTQWWEWRT

  • TWTQWWEWRTQWWTW

  • TWTQWWEWRTQWWTWQTTQQ

  • TWTQWWEWRTQWWTWQTTQQTRRTT
    / $>

    功能记忆过去的执行并存储结果,并再次使用它!



    我没有问这个!



    我想要输出类似于以下内容:




    • IPS30

    • DQ6BE

    • E70IH

    • XGHBR

    • 7LMN5



    我该如何达到这个结果?

    解决方案

    一个很好的问题。

    Rebol代码实际上是最好的想法作为非常程式化的数据结构。该数据结构恰好是可执行的。例如,从@ WiseGenius的建议:

      make-password:func [长度] [
    字符:QWERTYUIOPASDFGHJKLZXCVBNM1234567890
    密码:复制
    循环长度[附加密码(选择字符随机长度)]
    密码
    ]

    查看包含追加密码... 。那个街区在那里成像它真正的样子是:

     字符:**指向字符串的指针! 0xSSSSSSS1 ** 
    密码:复制**指向字符串的指针! 0xSSSSSSS2 **
    循环长度**指向块的指针! 0xBBBBBBB ** **
    密码

    当解释器加载它们时,所有系列都以这种方式工作。字符串,块,二进制文件,路径,parens等等。假设它是一路下来的乌龟,如果你继续指向那个指针,块0xBBBBBBBB在内部:

     附加密码**指向paren的指针! 0xPPPPPPPP ** 

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

     >> inner:[a] 

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

    >>追加内部'b

    >>探针外部
    [[ab] [ab]]

    这可能是混淆的来源对于新手来说,但是一旦你理解了数据结构,你就会开始知道何时使用COPY。

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

    pre $ f $ func [] [
    data:[]
    append data'something
    ]

    源foo

    foo
    foo

    源foo



    产生一个可能令人惊讶的结果:

      foo:func [] [
    data:[]
    附加数据'something
    ]

    foo:func [] [
    data:[something something ]
    附加数据'something
    ]

    我们将 foo 几次,看起来函数的源代码正在改变。从某种意义上说,它是自我修改的代码。



    如果这让你感到困扰,R3-Alpha中有工具可以攻击它。您可以使用PROTECT来保护函数体免受修改,甚至可以创建自己的替代程序,例如FUNC和FUNCTION这样的程序,它将为您执行。 (PFUNC?PFUNCTION?)在Rebol版本3中,您可以编写:

      pfunc:func [spec [block!] body [block !]] [
    使功能! protect / deep copy / deep reduce [spec body]
    ]

    foo:pfunc [] [
    data:[]
    附加数据'something
    ]

    foo

    当你运行时,你会得到:

      ***错误
    **脚本错误:受保护的值或系列 - 无法修改
    **其中:append foo请尝试执行-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:**指向块的指针! 0xBBBBBBBB ** 

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



    注意,每次运行函数时,数据将被重新分配。但一次又一次地重新分配......原始块指针!这就是为什么你必须复制,如果你想每次运行一个新的块。



    在评估规则中掌握基本的简单性是令人头晕的有趣性的一部分。这就是简单装扮成一种语言的方式(你可以用自己的方式扭曲)。例如,没有多重任务:

      a:b:c:10 

    这就是评估者作为SET-WORD命中 a:!符号并说好吧,让我们将变量 a 与其绑定上下文中的变量关联起来,以及下一个完整表达式产生的内容。 b:也一样。 c:完成相同操作,但由于整数值为10 ...,因此的计算结果为10,因此它看起来像多重赋值。

    >

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




    相关注释

    有些人认为你绝对不应该写 copy [] <因为(a)你可能养成忘记写COPY的习惯,并且(b)每次做这个时你都会制作一个没用过的系列。这个空白系列模板被分配,必须由垃圾收集器扫描,并且没有人真正触及它。



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


    In rebol I have written this very simple function:

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

    When I run this multiple times in a row things get really confusing:

    loop 5 [print make-password 5]
    

    Gives (for example) this output:

    • TWTQW
    • TWTQWWEWRT
    • TWTQWWEWRTQWWTW
    • TWTQWWEWRTQWWTWQTTQQ
    • TWTQWWEWRTQWWTWQTTQQTRRTT

    It looks like the function memorised the past executions and stored the result and than used it again!

    I did not ask this!

    I would like to have output similar to the following:

    • IPS30
    • DQ6BE
    • E70IH
    • XGHBR
    • 7LMN5

    How can I achieve this result?

    解决方案

    A good question.

    Rebol code is actually best thought of as a very stylized data structure. That data structure "happens to be executable". But you need to understand how it works.

    For instance, from @WiseGenius's suggestion:

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

    Take a look at the block containing append password.... That block is "imaged" there; what it really looks like under the hood is:

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

    All series are working this way when they are loaded by the interpreter. Strings, blocks, binaries, paths, parens, etc. Given that it's "turtles all the way down", if you follow through to that pointer, the block 0xBBBBBBBB is internally:

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

    One result of this is that a series can be referenced (and hence "imaged") in multiple places:

    >> inner: [a]
    
    >> outer: reduce [inner inner]
    [[a] [a]]
    
    >> append inner 'b
    
    >> probe outer
    [[a b] [a b]]
    

    This can be a source of confusion for newcomers, but once you understand the data structure you begin to know when to use COPY.

    So you've noticed an interesting implication of this with functions. Consider this program:

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

    That produces a possibly-surprising result:

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

    We call foo a couple of times, it appears that the function's source code is changing as we do so. It is, in a sense, self-modifying code.

    If this bothers you, there are tools in R3-Alpha for attacking it. You can use PROTECT to protect function bodies from modification, and even create your own alternatives to routines like FUNC and FUNCTION that will do it for you. (PFUNC? PFUNCTION?) In Rebol version 3 you can write:

    pfunc: func [spec [block!] body [block!]] [
        make function! protect/deep copy/deep reduce [spec body]
    ]
    
    foo: pfunc [] [
        data: []
        append data 'something
    ]
    
    foo
    

    When you run that you get:

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

    So that forces you to copy series. It also points out that FUNC is just a function! itself, and so is FUNCTION. You can make your own generators.

    This may break your brain and you may run screaming saying "this is not any sane way to write software". Or maybe you will say "my God, it's full of stars." Reactions may vary. But it is fairly fundamental to the "trick" that powers the system and gives it wild flexibility.

    (Note: The Ren-C branch of Rebol3 has fundamentally made it so that function bodies--and source series in general--are locked by default. If one wants a static variable in a function, you can say foo: func [x <static> accum (copy "")] [append accum x | return accum] and the function will accumulate state in accum across calls.)

    I'll also suggest paying close attention to what is actually happening on each run. Before you've run the foo function, data has no value. What happens is each time we execute the function and the evaluator sees a SET-WORD! followed by a series value, it performs the assignment to the variable.

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

    After that assignment, you'll have two references to the block in existence. One is its existence in the code structure that was established at LOAD time, before the function had ever been run. The second reference is the one that was stored into the data variable. It's through this second reference that you are modifying this series.

    And notice that data will be reassigned each time the function is run. But reassigned to the same value over and over again...that original block pointer! This is why you have to COPY if you want a fresh block on every run.

    Grasping the underlying simplicity in the evaluator rules is part of the giddy interesting-ness. This is how the simplicity was dressed up to make a language (in a way you could twist to your own means). For instance, there is no "multiple-assignment":

    a: b: c: 10
    

    That's just the evaluator hitting a: as a SET-WORD! symbol and saying "okay, let's associate the variable a in its binding context with whatever the next complete expression produces.". b: does the same. c: does the same but hits a terminal because of the integer value 10...and then also evaluates to 10. So it looks like multiple-assignment.

    So just remember that the original instance of a series literal is the one hanging in the loaded source. If the evaluator ever gets around to doing this kind of SET-WORD! or SET assignment, it will borrow the pointer to that literal in the source to poke into the variable. It's a mutable reference. You (or the abstractions you design) can make it immutable with PROTECT or PROTECT/DEEP, and you can make it not-a-reference with COPY or COPY/DEEP.


    Related Note

    Some argue that you should never write copy []...because (a) you might get in the habit of forgetting to write the COPY, and (b) you are making an unused series every time you do it. That "blank series template" gets allocated, has to be scanned by the garbage collector, and no one ever actually touches it.

    If you write make block! 10 (or whatever size you want to preallocate the block) you avoid the issue, save a series, and offer a sizing hint.

    这篇关于为什么功能“有记忆”在REBOL?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆