2014-09-19 13 views
6

In Rebol Ho scritto questo molto semplice funzione:Perché la funzione "ha memoria" in REBOL?

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

Quando eseguo questo più volte di seguito le cose si fanno davvero confusa:

loop 5 [print make-password 5] 

Gives (per esempio) questa uscita:

  • TWTQW
  • TWTQWWEWRT
  • TWTQWWEWRTQWWTW
  • TWTQWWEWRTQWWTWQTTQQ
  • TWTQWWEWRTQWWTWQTTQQTRRTT

Sembra che la funzione memorizzato le esecuzioni del passato e memorizzato il risultato e quello utilizzato di nuovo!

Non l'ho chiesto!

Vorrei avere un output simile al seguente:

  • IPS30
  • DQ6BE
  • E70IH
  • XGHBR
  • 7LMN5

Come posso raggiungere questo risultato ?

+0

@Joachim No, si comporta esattamente allo stesso modo. – Caridorc

risposta

5

una buona domanda.

Il codice di riferimento è in realtà considerato come una struttura di dati molto stilizzata. Quella struttura dati "sembra essere eseguibile". Ma devi capire come funziona.

Per esempio, da @ suggerimento di WiseGenius:

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

Date un'occhiata al blocco contenente append password....Quel blocco è "immaginato" lì; quello che sembra davvero come sotto il cofano è:

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

Tutte le serie stanno lavorando in questo modo quando vengono caricati dall'interprete. Strings, blocchi, file binari, sentieri, parens, ecc Dato che si tratta di "tartarughe fino in fondo", se si seguono attraverso tale puntatore, il blocco 0xBBBBBBBB è internamente:

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

Un risultato di questo è che una serie può fare riferimento (e quindi "ripreso") in più posti:

>> inner: [a] 

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

>> append inner 'b 

>> probe outer 
[[a b] [a b]] 

questo può essere fonte di confusione per i nuovi arrivati, ma una volta capito la struttura dei dati si comincia a sapere quando usare COPY.

Quindi hai notato un'interessante implicazione di questo con le funzioni. Considerate questo programma:

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

source foo 

foo 
foo 

source foo 

che produce un risultato forse-sorprendente:

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

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

Noi chiamiamo foo un paio di volte, sembra che codice sorgente della funzione sta cambiando, come lo facciamo. È, in un certo senso, codice auto-modificante.

Se questo ti infastidisce, ci sono strumenti in R3-Alpha per attaccarlo. Puoi usare PROTECT per proteggere i corpi delle funzioni dalle modifiche e persino creare le tue alternative alle routine come FUNC e FUNCTION che lo faranno per te. (?? PFUNC PFUNCTION) In Rebol versione 3 è possibile scrivere:

pfunc: func [spec [block!] body [block!]] [ 
    make function! protect/deep copy/deep reduce [spec body] 
] 

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

foo 

Quando si esegue che si ottiene:

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

In modo che le forze di copiare serie. Sottolinea inoltre che FUNC è solo una funzione! stesso, e così è FUNZIONE. Puoi creare i tuoi generatori.

Questo potrebbe spezzare il cervello e potresti correre urlando dicendo "questo non è un modo sensato di scrivere software". O forse dirai "mio Dio, è pieno di stelle". Le reazioni possono variare. Ma è abbastanza fondamentale per il "trucco" che alimenta il sistema e gli dà una flessibilità selvaggia.

(Nota: La Ren-C branch of Rebol3 ha fondamentalmente fatto in modo che gli enti di funzione - e serie source in generale - sono bloccati di default Se si vuole una variabile statica in una funzione, si può dire foo: func [x <static> accum (copy "")] [append accum x | return accum] e la. la funzione si accumulerà stato in accum tra le chiamate.)

sarò anche suggerire prestando particolare attenzione a ciò che sta realmente accadendo in ogni esecuzione. Prima di eseguire la funzione foo, i dati non hanno alcun valore. Quello che succede è ogni volta che eseguiamo la funzione e il valutatore vede una SET-WORD! seguito da un valore di serie, esegue l'assegnazione alla variabile.

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

Dopo che l'assegnazione, avrete due riferimenti al blocco esistenti.Uno è la sua esistenza nella struttura del codice stabilita al momento LOAD, prima che la funzione fosse mai stata eseguita. Il secondo riferimento è quello che è stato memorizzato nella variabile dati. È attraverso questo secondo riferimento che stai modificando questa serie.

e notare che i dati verranno riassegnate ogni volta che la funzione viene eseguito. Ma riassegnato allo stesso valore più e più volte ... quel puntatore di blocco originale! Questo è il motivo per cui devi COPIARE se vuoi un nuovo blocco per ogni corsa.

Afferrare la semplicità sottostante nelle regole di valutazione è parte della vertiginosa interes- sione. Questo è il modo in cui la semplicità è stata vestita per creare un linguaggio (in un modo in cui potresti torcere a modo tuo). Per esempio, non v'è alcuna "multiple-assegnazione":

a: b: c: 10 

Questo è solo il valutatore colpire un: come un set-PAROLA! simbolo e dicendo "okay, associamo la variabile a nel suo contesto vincolante con qualsiasi altra espressione produca.". b: fa lo stesso. c: fa lo stesso, ma colpisce un terminale a causa del valore intero 10 ... e quindi anche il valore di viene valutato a 10. Quindi sembra un'assegnazione multipla.

Quindi ricorda che l'istanza originale di una serie letterale è quella che si trova nella sorgente caricata. Se il valutatore riesce a fare questo tipo di SET-WORD! o SET assignment, prenderà a prestito il puntatore a quel letterale nella fonte per attirare la variabile. È un riferimento mutevole. Tu (o le astrazioni che disegni) puoi renderlo immutabile con PROTECT o PROTECT/DEEP, e puoi renderlo non-un-riferimento con COPY o COPY/DEEP.


nota correlata

Alcuni sostengono che non si dovrebbe mai scrivere copia [] ... perché (a) si potrebbe ottenere l'abitudine di dimenticare di scrivere il COPY, e (b) stai facendo una serie inutilizzata ogni volta che lo fai. Quel "modello di serie vuoto" viene assegnato, deve essere scansionato dal garbage collector e nessuno lo tocca mai.

Se si scrive fare blocco! 10 (o qualunque dimensione si voglia preassegnare il blocco) si evita il problema, si salva una serie e si offre un suggerimento per il dimensionamento.

+0

Sembra che il tuo codice sia rotto: fornisce 'Script Error: argomento previsto del corpo della funzione type: block' (Sto usando REBOL 2.7 View). – Caridorc

+2

@Caridorc Rebol3 ha ridefinito il significato di FUNCTION per essere quello che Rebol2 ha chiamato FUNCT * (che è [in linea con Red] (http://www.red-lang.org/p/contributions.html), anche) *. Tendo a trovare la funzione Rebol3, che fa la raccolta automatica di SET-WORD! elementi nel corpo come parametri **/local **, per essere il nome più elegante e il miglior comportamento predefinito per i nuovi utenti, quindi tendo a digitarlo rispetto al primitivo FUNC di livello inferiore. Ma l'ho modificato in FUNC nel mio esempio nel caso in cui l'esempio sia considerato in Rebol2 e Rebol3. – HostileFork

+1

@Caridorc Nota anche ora hai i punti necessari per l'accesso a [Rebol e Red SO chat] (http://chat.stackoverflow.com/rooms/291/rebol-and-red) – HostileFork

2

Per impostazione predefinita, questa notazione non copia il valore della stringa "" in password. Invece, imposta password in modo che punti a quella stringa che si trova nel blocco del corpo della funzione. Pertanto, quando esegui append su password, in realtà stai aggiungendo quella stringa a cui punta, che si trova nel blocco del corpo della tua funzione. In realtà stai modificando parte del blocco del corpo della funzione. Per vedere che cosa sta succedendo, è possibile utilizzare ?? esaminare la funzione per vedere cosa succede ad esso ogni volta che si utilizza:

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

loop 5 [ 
    print make-password 5 
    ?? make-password 
] 

Questo dovrebbe dare qualcosa di simile:

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 
] 

Per copiare la stringa a password piuttosto che punto ad esso, provate questo invece:

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

La tua risposta è rigida, ma @HostileFork ha dato una risposta più profonda. – Caridorc

+0

@Caridorc Speravo che lo farebbe. – WiseGenius

+0

@Caridorc Contento di averlo trovato utile. L'ho approfondito un po 'per chiarire un paio di altri problemi. – HostileFork

2

Non avendo abbastanza reputazione per commentare la risposta di HostileFork, reagisco in questo modo. Riguarda la tua "Nota correlata", che mi indica qualcosa di cui non ero mai stato a conoscenza.

"Alcuni discutono" suggerisce che tu non sei tra questi, ma comunque mi hai fatto pensare che scrivere meglio str: make string! 0 e blk: make block! 0 da ora in poi, non solo all'interno delle funzioni. Il suggerimento di dimensionamento mi ha sempre lasciato perplesso. Ci sono delle raccomandazioni su cosa scegliere qui nel caso in cui non hai idea della grandezza finale? (Non meno delle tue aspettative minime naturalmente, e anche non più del massimo.)

Problemi correlati