Why does the function "have a memory" in REBOL?

In rebol, I wrote this very simple function:

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

When I run this several times in a row, everything becomes very confusing:

 loop 5 [print make-password 5] 

Gives (for example) this output:

  • TWTQW
  • TWTQWWEWRT
  • TWTQWWEWRTQWWTW
  • TWTQWWEWRTQWWTWQTTQQ
  • TWTQWWEWRTQWWTWQTTQQTRRTT

It seems that the function remembered past performances and saved the result, and then used it again!

I did not ask about it!

I would like to have a conclusion similar to the following:

  • IPS30
  • DQ6BE
  • E70IH
  • XGHBR
  • 7LMN5

How can I achieve this result?

+6
source share
3 answers

Good question.

Rebol code is actually best understood as a very stylized data structure. This data structure is "executable." But you need to understand how it works.

For example, from the @WiseGenius 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... This block is "displayed" there; what actually looks under the hood:

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

All series work this way when they are loaded by the interpreter. Lines, blocks, binaries, paths, parens, etc. Given that it is "turtles all the way down," if you go to this pointer, the 0xBBBBBBBBBB block is internally:

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

One of the results of this is that the series can be referenced (and therefore "portrayed") in several places:

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

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

So, you have noticed the interesting meaning of this with functions. Consider this program:

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

This gives an unexpected result:

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

We call foo couple of times, it seems that the source code of the function changes when we do this. This is, in a sense, self-modifying code.

If this bothers you, there are attack tools in R3-Alpha. You can use PROTECT to protect functional objects from modification and even create your own alternatives to routines, such as FUNC and FUNCTION, that will do this for you. (PFUNC? PFUNCTION?) In version 3 of Rebol 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 this, you will get:

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

So this makes you copy the series. He also points out that FUNC is just a feature! in itself, as well as the FUNCTION function. You can create your own generators.

It can break your brain, and you can scream, saying, "This is not a smart way to write software." Or maybe you say, "My God, he is full of stars." Reactions may vary. But this is quite fundamental to the β€œtrick” that controls the system and gives it wild flexibility.

(Note: the Ren-C branch of Rebol3 fundamentally did this so that function bodies and source series in general are blocked by default. If you want 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 for calls.)

I also suggest paying close attention to what actually happens in each run. Before running the foo function, the data does not matter. What happens every time we perform a function, and the evaluator sees SET-WORD! followed by a series value, it assigns a variable.

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

After this assignment, you will have two links to the existing block. One of them is its existence in the code structure that was installed during LOAD before the function was run. The second link is the one that was stored in the data variable. It is through this second link that you are modifying this series.

And note that the data will be reassigned each time the function starts. But the same value is reassigned again and again ... this original block pointer! That is why you should COPY if you need a fresh block for each run.

The grappling of basic simplicity in the rules of the appraiser is part of dizzying interest. So simplicity was dressed to make a language (that way, you could twist your own means). For example, there is no "multiple use":

 a: b: c: 10 

This is just an estimate by pressing a: like SET-WORD! symbol and say "ok, let me bind the variable a in the context of the binding to what creates the next full expression." b: does the same. c: does the same, but gets to the terminal due to the integer value 10 ... and then is also evaluated to 10. Thus, it looks like a multiple assignment.

So just remember that the original instance of the series literal is the one that hangs in the loaded source. If the evaluator ever handles this type of SET-WORD! or SET, it will borrow a pointer to this literal in the source to push the variable. This is a mutable link. You (or the abstractions you are developing) can make it immutable with PROTECT or PROTECT / DEEP, and you can do this without reference using COPY or COPY / DEEP.


Topic Note

Some argue that you should never write copy [] ... because (a) you may get used to forgetting to write COPY, and (b) you make an unused series every time you do it. This "empty series template" gets highlighted, must be checked by the garbage collector, and no one ever touches it.

If you write make block! 10 (or no matter what size you want to distribute over the block), you avoid the problem, save the series and offer a size hint.

+5
source

By default, this notation does not copy the value of the string "" to password . Instead, it sets a password to indicate the line that is in the function body block. Therefore, when you execute append on password , you actually add to the line that it points to that sits in the body block of your object. You actually change the part of the function body block. To find out what is happening you can use ?? to test your function, to keep track of what happens to it every time you use it:

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

This should give you something like:

 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 ] 

To copy a string to password instead of pointing to it, try this instead:

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

Not having enough reputation to comment on HostileFork's answer, I react like that. This is about your β€œBound Note” that tells me something that I never knew about.

"Some people claim" that you are not among them, but nevertheless you made me think that it is better to write str: make string! 0 and blk: make a block! 0 , not just inside functions. Specifying the size always puzzled me. Are there any recommendations on what to choose here if you have no idea about the final value? (No less than your minimum expectation, of course, and also no more than maximum.)

+2
source

Source: https://habr.com/ru/post/975536/


All Articles