Ricordate quando parlavo del mio piccolo doctest per ruby?
Ok, continua ad essere sporco e puzzolente, ma adesso ha un paio di feature in più, tra cui la possibilità di effettuare il replay di una sessione passata.
Questo significa che se avevate un test-esempio
>> a = 10
=> 10
>> b = 20
=> 20
>> def sum(a,b)
>> a+b
>> end
=> nil
>> sum(a,b)
=> 30
potete effettuare il replay della sessione ed andare avanti con
>> def triplesum(a,b)
>> sum(sum(sum(a,b),b),b)
>> end
=> nil
>> triplesum(a,b)
=> 70
Che è molto carino, e utile imo.
Ora, dove sta il problema? Primo, nel modo in cui funziona eval, secondo, nel modo in cui è scritto IRB.
evil eval
Che eval sia fondamentalmente malvagio è una cosa rinomata, ma per scrivere un REPL rimane la cosa più sensata.
Quello che potreste non sapere è che in ruby eval() non può definire una variabile nello scope in cui è richiamata. O quasi.
Lasciate stare IRB e provate questo:
ruby -e \"eval('a=10');a\"
Se tutto funziona normalmente dovreste beccarvi un bel NameError. Questo perché il parser non ha mai visto nessuna a nello scope corrente.
Se però provate questo:
ruby -e \"eval('a=10');p eval('a')\"
vedrete stampato un bel 10 che dimostra che la variabile esiste solo che non è accessibile con il solito approccio.
good IRB
IRB però vi frega in questo, guardate un po’:
>> eval(\"a=10\")
=> 10
>> a
=> 10
Perché? Perché ovviamente IRB usa internamente eval, non fa il parsing come l’interprete farebbe normalmente, e quindi tutto funziona.
good require
Se caricate un file con require() le variabili locali rimangono.. beh.. locali, e quindi non sono accessibili all’esterno. Allora come facciamo a caricare le variabili locali che avevamo nella sessione precedente?
Non possiamo fare un dump del codice su un file e caricarlo con questo metodo, ma possiamo usare eval() per definirle in un oggetto Binding e poi convincere IRB che deve usare quell’oggetto come spazio di lavoro.
evil IRB
Solo che IRB, di default, non permette di effettuare questa cosa. C’è un casino di setup quando la console interattiva viene inizializzata, che va dal caricare l’encoding al verificare la configurazione di default all’impostare il nome del programma, e tutto questo è fatto tramite IRB.start che non ammette alcun parametro utile per i nostri scopi.
Soluzione
Qual’è la prima forma di riuso? Il copia&incolla, esatto. Quindi per definire la nuova funziona ci si limita a ricopiare il codice di quella originale in modo quasi identico, cambiandolo dove serve.
In attesa che qualcuno ci dia una versione di IRB che ci semplifichi la vita, potete quindi usare il mio IRB.start_in_binding che crea una console per il binding dato (o per TOPLEVEL_BINDING).
Pare funzionare, in caso non andasse potete ravanere nei sorgenti di IRB da soli o sfruttare il lavoro fatto da flgr in ruby-breakpoint