Variable value in foreach of Red language

2019-07-13 06:35发布

问题:

I am using following code to add multiple GUI elements to a view via a foreach loop:

myRange: function [n][  ; to produce a vector of [1 2 3 4... n]
  vv: make vector! n 
  i: 1
  foreach j vv [
    vv/:i: i
    i: i + 1
    if i > n [break]]
  vv ]

view collect[ 
    foreach i myRange 10 [  
        print append "i in loop: " i
        keep [ t: text ]  keep append "message number: " i
        keep [field "entry"     button "Click" [t/text: "clicked"] return]
            ] ]

All GUI elements are being produced. But the code append "message number: " i is showing value of i to be 12345678910 in all text elements and not 1, 2, 3... 10 for different text elements.

Also, print append... statement is producing following output:

i in loop: 1
i in loop: 12
i in loop: 123
i in loop: 1234
i in loop: 12345
i in loop: 123456
i in loop: 1234567
i in loop: 12345678
i in loop: 123456789
i in loop: 12345678910

Moreover, clicking any button changes text of only the last added text element.

Where is the problem and how can it be solved? Thanks for your help.


Edit: It seems the language is converting my code from:

for i 1 10 1 [  
   print append "i in loop: " i  ]

to:

a_variable: "i in loop"
for i 1 10 1 [  
   print append a_variable i  ]

Which is not what I and (I think) most users want. In most languages a string "i in loop" will be taken as a constant and not converted to a variable since user has not specified it so. IMHO it will be easier for users of other languages to come here if such basic conventions are not altered.

回答1:

Whenever you see something like this, it means you failed to create a new series and are reusing an existing series.

To get around that you need to create a new series with copy

Eg.

print append copy "i in loop: " i

Rebol3/ren-c no longer has this problem because source code is immutable and so you will get an error message with such code.



回答2:

Rebol and Red reuses series (e.g. strings, blocks) as much as possible if you do not tell them otherwise by initializing anew with copy, make etc. So your issue should go away if you write

append copy "message number: " i


回答3:

As the other answers suggest, you are only using a single string for your message and would need to be copied.

As to the other concern—you should take a wee look at the code you're generating (as I suggested elsewhere, you can pop a little PROBE to examine the output of the COLLECT function):

[
    t: text "message number: 1" field "entry" button "Click" [t/text: "clicked"] return 
    t: text "message number: 2" field "entry" button "Click" [t/text: "clicked"] return 
    t: text "message number: 3" field "entry" button "Click" [t/text: "clicked"] return 
    t: text "message number: 4" field "entry" button "Click" [t/text: "clicked"] return 
    t: text "message number: 5" field "entry" button "Click" [t/text: "clicked"] return 
    t: text "message number: 6" field "entry" button "Click" [t/text: "clicked"] return 
    t: text "message number: 7" field "entry" button "Click" [t/text: "clicked"] return 
    t: text "message number: 8" field "entry" button "Click" [t/text: "clicked"] return 
    t: text "message number: 9" field "entry" button "Click" [t/text: "clicked"] return 
    t: text "message number: 10" field "entry" button "Click" [t/text: "clicked"] return
]

As you can see, you're constantly reassigning t so that in the end, it only refers to the last face.

A few options exist here—most prominent is to generate the name you're assigning the text face to. Within your FOREACH loop:

keep compose/deep [
    (to set-word! rejoin ["t-" i])
    text (rejoin ["Message Number: " i])
    field "entry"
    button "Click" [
        set in (to word! rejoin ["t-" i]) 'text "clicked"
    ]
    return
]

Note that in order to simplify the block creation, I used this line:

set in (to word! rejoin ["t-" i]) 'text "clicked"

This composes to (in the first instance):

set in t-1 'text "clicked"

IN returns the given word ('text) bound to the given context (the face object t-1) which is then SET to "clicked".

UPDATE

This method doesn't even use a word name, just uses a common parent to connect the button to the label:

view collect [
    keep [below space 0x0]
    foreach i myRange 10 [
        keep compose/deep [
            panel [
                origin 0x0
                text (rejoin ["Message Number: " i])
                field "entry"
                button "Click" [face/parent/pane/1/text: "clicked"]
            ]
        ]
    ]
]