Category Archives: Ruby

Ruby Tk – tutto quello che serve

r7
Situazione di quasi panico, qui.
Sì perché nel post precedente vi ho raccontato troppe cose e adesso sapete tutto di come gestire l’interfaccia grafica con Ruby 🙄

Prendiamo per esempio i pulsanti: trovate tutto nella pagina Ruby/TK – Button Widget. Oppure per il testo (label) qui: Ruby/TK – Label Widget e così via fino alle cose complicate, da usare solo se davvero necessarie, come Ruby/TK – Notebook Widget, che sarebbero poi i tab.

Quindi, come si sarà ormai capito usate la guida Ruby/TK Guide che ha tutti i casi base.

Poi sta a voi assemblare i widget gli uni dentro agli altri, così:

#!/usr/bin/ruby
# encoding: UTF-8

#nb1.rb

require 'tk'
require 'tkextlib/tile'

root = TkRoot.new
root.title = "Window"

n = Tk::Tile::Notebook.new(root) {
    height 110
    place('width' => 200, 'height' => 100, 'x' => 10, 'y' => 10)
}

f1 = TkFrame.new(n)
f2 = TkFrame.new(n)
f3 = TkFrame.new(n)

image = TkPhotoImage.new
image.file = "r7.gif"
label = TkLabel.new(f2)
label.image = image
label.place('height' => image.height,
            'width' => image.width,
      	    'x' => 10, 'y' => 10)

n.add f1, :text => 'One', :state =>'disabled'
n.add f2, :text => 'Two'
n.add f3, :text => 'Three'

Tk.mainloop

nb1

È richiesta questa gif (sì, pare che Tk accetti solo le gif) con il nome r7.gif:

r7

Che ne dite se il corso Ruby termina qui? Ma sempre a disposizione per integrazioni, per richieste specifiche (e possibili) 😛
Però presto un post sulla comparazione di Ruby con linguaggi simili, forse 😯

Ruby Tk – Una prima finestra

r6
Cosa fare dopo l’Hello World! del post precedente?
D’oh! (cit.): è ora di iniziare con una finestra vera, ci mettiamo dentro i frame e un paio di pulsanti per vedere qualche interazione. Ecco un primo esempio (f1.rb):

#!/usr/bin/ruby
# encoding: UTF-8

#f1.rb

require "tk"

f1 = TkFrame.new {
    relief 'sunken'
    borderwidth 3
    background "red"
    padx 15
    pady 20
    pack('side' => 'left')
}

f2 = TkFrame.new {
    relief 'groove'
    borderwidth 1
    background "yellow"
    padx 10
    pady 10
    pack('side' => 'right')
}

TkButton.new(f1) {
    text 'Button1'
    command {puts "push button1"}
    pack('fill' => 'x')
}

TkButton.new(f1) {
    text 'Button2'
    command {puts "push button2"}
    pack('fill' => 'x')
}

TkButton.new(f2) {
    text 'Quit'
    command 'exit'
    pack('fill' => 'x')
}

Tk.mainloop

f1

Esaminiamo brevemente il codice. Definiamo due frame (righe 8 e 17) di cui settiamo alcune proprietà (ce ne potrebbero essere anche altre, come vedremo in seguito).

In seguito definiamo 3 pulsanti (righe 25, 32 e 38) i primi due appartenenti al frame f1 e il terzo a f2. Per ogni pulsante definiamo il testo e il comando che viene eseguito quando questo viene premuto. Notare che i comandi possono essere istruzioni sia di Ruby (come puts) che di Tk come exit.
Ma, abbiamo visto nei post precedenti che tutto dove può esserci un’istruzione può esserci una funzione; ciò dovrebbe senz’altro essere possibile anche qui.
Proviamo (f2.rb):

#!/usr/bin/ruby
# encoding: UTF-8

#f2.rb

require "tk"

f1 = TkFrame.new {
    relief 'sunken'
    borderwidth 5
    background "red"
    padx 15
    pady 20
    pack('side' => 'left')
}

f2 = TkFrame.new {
    relief 'groove'
    borderwidth 10
    background "yellow"
    padx 2
    pady 2
    pack('side' => 'bottom')
}

TkButton.new(f1) {
    text 'Button1'
    command {puts "push button1"}
    pack('fill' => 'x')
}

TkButton.new(f1) {
    text 'Button2'
    command {messaggio 2}
    pack('fill' => 'x')
}

TkButton.new(f2) {
    text 'Quit'
    command {esci}
    pack('fill' => 'x')
}

def esci
    puts "bye!"
    Tk.exit
end

def messaggio n
    puts "pulsante #{n}"
end
Tk.mainloop

f2

Abbiamo aumentato i bordi dei frame per evidenziare la proprietà relief ma questo è una cosuccia. Più importante i command per Button2 (riga 34) e Quit (riga 40) che eseguono funzioni definite da noi.

Le funzioni seguono nello stesso file. Notare che in esci abbiamo dovuto qualificare exit come appartenente a Tk e che la funzione messaggio riceve un parametro, cosa già vista in passato.

Quindi, conclusione per questo post: mi sembra che Tk sia molto semplice e razionale, finora non abbiamo visto niente di realmente nuovo. E se continua così possiamo dire che ormai sappiamo tutto, o quasi.

Ah! una cosa: ho seguito, quasi fedelmente la guida Ruby/TK Guide.

Ruby inizio con l’interfaccia grafica

ruby-mini-logo
Finito l’esame degli elementi base del linguaggio oggi mi addentro nel magico mondo dell’interfaccia grafica, quella ormai giudicata indispensabile dappertutto. O quasi: Ruby viene spesso utilizzato per il web e allora niente finestre, pulsanti, menu, si usano quelli del browser. Inoltre ho visto che Ruby gode delle simpatie di lispisti e fautori della programmazione funzionale, OK, per il momento non c’interessa, torniamo al dunque.

L’interfaccia grafica standard (GUI) di Ruby è Tk. Tk inizialmente era la GUI per il linguaggio di scripting Tcl sviluppato da John Ousterhout. (Ehi, ricordo, una delle ultime cose che ho usato su Unix e poi ho tentato di riprendere quando ho iniziato con Linux!).

La parte non personale della frase precedente viene dalla guida che userò: Ruby/TK Guide.

Il collegamento tra Ruby e Tk è dentro Ruby ma il pacchetto Tk richiede l’installazione; per Windows si trova in  ActiveState’s ActiveTcl.

A questo punto siamo pronti per il nostro primo esempio, il classico Hello World!

#!/usr/bin/ruby
# encoding: UTF-8

#hw0.rb

require 'tk'

root = TkRoot.new { title "Hello, World!" }
TkLabel.new(root) do
   text 'Hello, World!'
   pack { padx 15 ; pady 15; side 'left' }
end
Tk.mainloop

hw0

Uhmmm! un po’ piccolina come finestra, ingrandisco

hw01

Sì, già un pochino meglio, c’è tutto.
Esaminiamo il codice: la prima riga è quella solita per Linux, nel mondo Windows viene vista come un semplice commento; la seconda, benché tecnicamente sia un commento ci consente di usare i caratteri accentati e poi segue un commento vero e proprio con il nome del file, sapete che sono un confusionario e mi dimentico delle cose.

La prima istruzione seria la troviamo alla riga 5, dove dichiariamo che è richiesto il modulo tk.
Alla riga 7 definiamo la finestra, root. Vedremo in seguito che con Tk si definisce un widget, si riempie (gerarchicamente) e si esegue.
Il nostro widget si chiama (come si usa abitualmente) root e nel definirlo gli passiamo il titolo. All’interno della finestra root inseriamo una TkLabel (etichetta), di cui settiamo il testo e la packiamo (impacchettiamo).
Per visualizzare il tutto basta richiamare il loop di Tk, riga 13.
Semplice vero?
Prima modifica naif, voglio la finestra più grande. Proviamo così:

#!/usr/bin/ruby
# encoding: UTF-8

#hw1.rb

require 'tk'

root = TkRoot.new { title "Hello, World!" }
TkLabel.new(root) do
   text '

      Hello, World!

      '
   pack { padx 15 ; pady 15; side 'left' }
end
Tk.mainloop

hw1

Hmmm, sì, cioè un po’, come dire… tutto qui?

No, dai, siamo appena agli inizi, ricordate che Ankh-Morpork non è stata costruita in un giorno (cit.) 🙂

Ruby – Regexp II

r5
Le espressioni regolari —regexp— sono uno strumento molto potente come sanno i programmatori Perl, awk e simili. Non sempre sono facili da usare, anzi…

regexp

Ci sono interi manuali, fatti di esempi, manca sempre quel che  serve 😦
Nel nostro caso abbiamo ancora un problema aggiuntivo: gli esempi riportati dalla nostra guida abituale sono riferiti a una versione vecchia e non funzionano. Propongo di usare Zetcode, ecco un esempio (z1.rb):

#!/usr/bin/ruby
# encoding: UTF-8

#z1.rb

puts "Her name is Jane" =~ /name/

puts $`
puts $&
puts $'

z1

La frase viene trattata con l’operatore ~ che restituisce il numero di parole e setta 3 variabili speciali contenenti rispettivamente il testo prima di quello cercato, quello cercato e quello successivo.

Naturalmente una condizione più realistica, con variabili sarebbe simile alla seguente (z2.rb):

#!/usr/bin/ruby
# encoding: UTF-8

#z2.rb

frase = "Her name is Jane"
n = frase =~ /name/

prima = $`
trovato = $&
dopo = $'
puts n
puts prima, trovato, dopo

Il risultato è, naturalmente, identico al precedente.
Ecco questo è un esempio elementare, siamo solo all’inizio. Ma non credo serva per un corso introduttivo, chi fosse interessato può googlare, per esempio ecco questo, questo e questo.

Basta con le regexp, il prossimo post sarà, salvo imprevisti, sulla grafica: finestre, pulsanti e roba simile 😉

Ruby – Regexp I

r4
O espressioni regolari. Potenti ma una brutta bestia da ammaestrare. Tranne che per i nerd linuxiani, loro sanno che ci sono da sempre, dagli albori di Unix.
Per tutti gli altri una brevissima intro. È un po’ come quando per dire OK usate due-punti + trattino + chiusa-parentesi che diventa così 🙂 o così 😦 se la parentesi è aperta.
Ma servono per altre cose, come vedremo.
Lanciamo il nostro interprete Ruby (con l’alias che abbiamo definito all’inizio del corso; in alternativa possiamo usare irb --simple-prompt):

re0

sì, // è una regexp, appartenente alla classe Regexp. Adesso vediamo cosa può fare.

re1

match ci dice se la nostra regexp è contenuta nella stringa; si può indagare meglio, così:

re2

L’operatore =~ restituisce la posizione di dove inizia la regexp (il primo carettere della stringa è lo zeresimo, vero che lo ricordiamo tutti?). Secondo la filosofia di Ruby si può usare in entrambi i modi proposti. Inoltre, al solito, i caratteri maiuscoli sono distinti dai minuscoli.

La tastiera italiana manca del simbolo ~ e per inserirlo c’è bisogno di un piccolo giochino, attenzione a non intrecciarvi i diti:

  • Windows: Alt + 126 sul tastierino numerico, se questo manca Alt + Fn + 126
  • Linux: AltGr + ì

Suggerimento: passate a Linux, è più facile e versatile.

Ma il bello delle regexp è che trovano le stringhe con i caratteri speciali, esempio

re3

Come si vede il punto vale per qualsiasi carattere, posso continuare a fare confusione tra Sonia e Sofia (tanto loro ormai lo sanno e si sono rassegnate, entrambe).
Ma i caratteri speciali sono millemila, per esempio il maiuscolo/minuscolo lo gestiamo con i:

re4

Se uno si trova a gestire spesso questa roba gli conviene fare un salto in qualche pagina specializzata, per esempio:

Qui solo una tabella riassuntiva

[abc]     ogni singolo carattere a, b, c
[^abc]    ogni singolo carattere diverso da a, b, c
[a-z]     ogni singolo carattere nell'intervallo a-z
[a-zA-Z]  ogni singolo carattere nell'intervallo a-z e A-Z
^         inizio della linea
$         fine della linea
\A        inizio della stringa
\z        fine della stringa
.         ogni singolo carattere
\s        ogni carattere spazio (anche, p.es. iltab)
\S        ogni carattere non spazio
\d        ogni cifra numerica [0-9]
\D        ogni carattere non cifra numerica
\w        ogni parola (lettera, numero, trattino basso)
\W        ogni carattere non \w
\b        ogni fine di parola
(...)     qualunque cosa dentro
(a|b)     a o b
a?        zero o una a
a*        zero o più a
a+        una o più a
a{3}      esattamente 3 a
a{3,}     3 o più a
a{3,6}    da 3 a 6 a

inoltre ci sono i modificatori

i      annulla la distinzione tra maiuscole e minuscole
m      il punto corrisponde all'a-capo
x      ignora gli spazi
o      esegue la sostituzione #(...)

Panico ❓ 😳 :mrgreen:

Forse è il caso di qualche esempio:

#!/usr/bin/ruby
# encoding: UTF-8

#  re1.rb
testo = """Ecco un piccolo testo con parole comuni,
per testare le espressioni regolari.
Qualcuna delle parole è ripetuta più d'una volta.
Brutta bestia le espressioni regolari!"""

parole = testo.scan(/\w+/)
puts "Le parole sono:"
print parole
puts
puts "ci sono #{parole.count} parole nel testo"
puts "ci sono #{parole.uniq.count} parole uniche nel testo"
puts "che sono:"
print parole.uniq
puts

re5

Notare il solito problema delle lettere accentate.

Un altro esempio, vogliamo estrarre i tag da un tweet, questo:

tweet

(no, non so cos’è; usato solo come esempio, è risultato il primo della ricerca).

#!/usr/bin/ruby
# encoding: UTF-8

#  re1.rb
testo = """【RETWEET】ONLY IF YOU WANT NEW FOLLOWERS #TeamFollowBack ✔ #TeamFollowWacky ✔ #500aDay ✔ #1000aDay ✔ #TFB ✔ #FollowFriday ✔ #FF #FollowNGain"""

parole = testo.scan(/#\w+/)
puts "I tag sono:"
print parole
puts

re6

OK, per oggi basta, con le regexp si continua prossimamente, ma sappiate che sono una brutta bestia, roba da Perl: in ogni script Perl sembra il luogo di un’esplosione nella fabbrica di segni di punteggiatura (cit.)
😉

Ruby – Thread II

r3

Deadlock (blocco)

Quando si usa Mutex bisogna prestare attenzione a evitare i blocchi di sistema, deadlocks. Questo capita quando tutti i thread sono in attesa per acquisire una risorsa bloccata da un altro thread.
A questo punto entrano in gioco le condition variables. Una condition variable è un semaforo associata a una risorsa a protezione di un mutex.
Quando necessiti di una risorsa non disponibile aspetti in una condition variable; quando un thead segnala che la risorsa è disponibile il thread che l’ha emessa esce dall’attesa e blocca la risorsa. Suona complicato? No, ecco un esempio (dlth.rb):

#!/usr/bin/ruby
# encoding: UTF-8

#  dlth.rb

require 'thread'
mutex = Mutex.new

cv = ConditionVariable.new
a = Thread.new {
   mutex.synchronize {
      puts "A: I have critical section, but will wait for cv"
      cv.wait(mutex)
      puts "A: I have critical section again! I rule!"
   }
}

puts "(Later, back at the ranch...)"

b = Thread.new {
   mutex.synchronize {
      puts "B: Now I am critical, but am done with cv"
      cv.signal
      puts "B: I am still critical, finishing up"
   }
}

a.join
b.join

dlth

Eccezioni di thread

Quando si verifica un’eccezione non gestita il programma termina. Vediamo cosa succede al verificarsi du un’eccezione all’interno di un thread (exth.rb):

#!/usr/bin/ruby
# encoding: UTF-8

#  exth.rb

require 'thread'
Thread.abort_on_exception = true

t = Thread.new do
    i = 4
    while i >= -1
        j = 24 / i
        puts j
        i -= 1
    end
end

sleep(10)
puts "Program completed"

exth

OK, al verificarsi dell’errore il programma abortisce. Modifichiamo la condizione di gestione dell’eccezione, riga 7 (exth1.rb):

#!/usr/bin/ruby
# encoding: UTF-8

#  exth.rb

require 'thread'
Thread.abort_on_exception = false # = -1
        j = 24 / i
        puts j
        i -= 1
    end
end

sleep(10)
puts "Program completed"

exth1

Come si vede in questo caso l’eccezione nel thread permette la continuazione del programma.

I thread hanno diversi altri metodi per gestire loe eccezioni, oltre a quella vista sopra. Sono peraltro riservate a un corso più approfondito 😉

Ruby – Thread I

r2

Ultimamente sono diventate normali le macchine multi-CPU, quindi è diventata possibile l’esecuzione di più istruzioni contemporaneamente. Anche se questo comporta un rivedere le nostre abitudini riguardo alla programmazione. E il linguaggio che usi deve consentirlo. Vediamo con Ruby.
In questo caso si parla di threads, in italiano sarebbero filoni ma mai sentito, caso mai sottoprocessi.
Considerate il caso di dover fare un certo lavoro: se siete in due il tempo necessario si dimezza (circa), se siete in tre ancora meglio e così via, finché riuscite a suddividere il lavoro e a coordinarvi.
Vediamo un esempio con Ruby (t0.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t0.rb

a = Thread.new{
    i = 1;
    while i<=10
        sleep(1)
        print "#{i} "
        i += 1
    end
    puts "\nfinito"
}

puts "Ecco l'esecuzione del thread lento"
a.join

t0

vediamo che i numeri compaiono a intervalli di un secondo, per via di sleep(1).

Niente di che, però esaminiamo quest’altro (t1.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t1.rb

def func1
   i = 0
   while i<= 2
      puts "func1 at: #{Time.now}"
      sleep(2)
      i += 1
   end
end

def func2
   j = 0
   while j<= 2
      puts "func2 at: #{Time.now}"
      sleep(1)
      j += 1
   end
end

puts "Started At #{Time.now}\n\n"
t1 = Thread.new{func1()}
t2 = Thread.new{func2()}
t1.join
t2.join
puts "\nEnd at #{Time.now}"

t1

Ecco che i due sottoprocessi t1 e t2 definiti alle righe 25 e 26 e lanciati alle righe 27 e 28 vengono eseguiti contemporaneamente come si può vedere dagli output interallacciati. Siccome il thread t2 è più veloce finisce prima.

Ogni thread esegue la sua funzione func1 per t1 e func2 per t2 ma questo non è necessario, ecco due thread che eseguono la stessa funzione (t2.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t2.rb

def func name, delay
   i = 0
   while i <= 2
      puts "#{name} #{Time.now}"
      sleep delay
      i += 1
   end
end
puts "Started At #{Time.now}\n\n"
t1=Thread.new{func "Thread 1:", 2}
t2=Thread.new{func "Thread 2:", 3}
t1.join
t2.join
puts "\nEnd at #{Time.now}"

t2

Come si vede i due processi hanno la stessa velocità. A seconda del carico della macchina può partire prima t1 o t2. Inoltre è inutile (anzi dannoso) eseguire contemporaneamente un numero di thread maggiore al numero delle CPU disponibili; per me solo due.

Visibilità delle variabili dei thread

Un thread può accedere (vedere) una variabile definita nel main, esempio (t3.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t3.rb

var = 0
puts "prima del thread var = #{var}"
a = Thread.new{
    var = 5
}
a.join
puts "dopo il thread var = #{var}"

t3

Vediamo cosa capita alla variabile definita nel thread (t4.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t4.rb

var_main = 0
puts "prima del thread var_main = #{var_main}"
a = Thread.new{
    var_main = 5
    var_thr = 42
}
a.join
puts "dopo il thread var_main = #{var_main}"
puts "la variabile definita nel thread var_thr = #{var_thr}"

t4

OPS! no, le variabili definite nel thread non sono visibili fuori dal thread.

Risorse condivise da più thread, mutex

A questo punto l’ottimo Karthikeyan propone uno script che però da me non funziona (p.141).
Forse non è colpa sua: lui fa riferimento a una versione precedente. Ciò confermerebbe la necessità di fare sempre molta attenzione alla versione che si usa, come già visto in passato.

In ogni caso vediamo cosa succede con 2 thread che usano le stesse variabili (t51.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t51.rb

require 'thread'

count1 = count2 = 0.0
difference = 0.0

counter = Thread.new do
   loop do
      count1 += 1
      count2 += 1
   end
end

spy = Thread.new do
   loop do
      difference += (count1 - count2).abs
   end
end

sleep 1
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

t51

OPS! difference dovrebbe essere zero, e invece no di parecchio. Il fatto è che il thread spy non è sincronizzato con counter e gli capita di calcolare la differenza mentre i valori count1 e count2 vengono aggiornati.

Il rimedio è di usare un mutex (da mutual exclusion) che sincronizza i due thread (t61.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t61.rb

require 'thread'
mutex = Mutex.new

count1 = count2 = 0.0
difference = 0.0

counter = Thread.new do
   loop do
      mutex.synchronize do
         count1 += 1
         count2 += 1
      end
    end
end

spy = Thread.new do
   loop do
       mutex.synchronize do
          difference += (count1 - count2).abs
       end
   end
end

sleep 1
mutex.lock
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

t61

OK? Sì, ovvio, è più lento. Ma corretto 🙂
Sui thread c’è ancora parecchio da dire, roba per un prossimo post 😉

Ruby – Procs

r1
Cos’è una proc? Possiamo prendere un pezzo di codice, compreso tra do e end e assegnarlo a una variabile. Questa variabile può essere usata, passata come argomento e così via, abbiamo creato una proc.
Le proc sono come le funzioni, con una differenza: sono oggetti. Vediamo un esempio (proc.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# proc.rb

say_hello = Proc.new do
    puts "Hello world!"
end

say_hello.call
say_hello.call
#

proc

La proc viene definita alle righe 6-8, definendo una variabile (say_hello nel nostro caso) uguale a una nuova proc.

Potrà così essere chiamata (cioè eseguita) tutte e volte che vogliamo, righe 10 e 11.

Naturalmente per essere utile una proc deve poter essere flessibile, ecco come passarle parametri (proc1.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# proc.rb

say_hello_to = Proc.new do |nome|
    puts "Hello #{nome}!"
end

say_hello_to.call "Janet"
say_hello_to.call "Juhan"
#

proc1

OK? Se avete letto i post precedenti non dovrebbero esserci cose nuove, la sintassi è quella usuale.

Passare proc ai metodi

In Ruby tutto è un oggetto (cit.), anche le proc, per cui possono essere passate ai metodi, esempio (ptom.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# ptom.rb --proc to method

def esegui_proc una_proc
    una_proc.call
end

say_hello = Proc.new do
    puts "Hello world!"
end

esegui_proc say_hello
#

ptom

Alle righe 4-6 creiamo il metodo esegui_proc che ha un parametro; niente di nuovo, sembra. No aspetta: il parametro è una proc, quella definita alle righe 10-12.

E la chiamata al metodo di riga 14 passa il parametro (cioè il nome della proc) nel modo usuale.
Se ci pensate un attimo vedrete che non poteva che essere così: in Ruby tutto è un oggetto (cit.)!

Proc come risultato di una funzione

O, come si dice normalmente, funzione ritornante una proc.
Abbiamo appena visto che una proc può essere passata a una funzione (nota: in Ruby “funzione” è spesso usata al posto di “metodo”, prassi corretta tranne nel caso in cui possa creare confusione) come parametro. Ma vediamo il caso opposto, cioè la funzione ce ritorna una proc (ret_p.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# ret_p.rb

def return_proc
    Proc.new do |nome|
        puts "La lunghezza di #{nome} è #{nome.length}"
    end
end

lun_nome = return_proc
lun_nome.call "Juhan van Juhan"
#

ret_p

La funzione return_proc (righe 6-10) definisce una proc senza nome. Ma è il valore che la funzione ritorna (piccolo ripassino: in Ruby una funzione ritorna il valore dell’ultima espressione, come in Lisp).

Per cui alla riga 12 assegniamo il nome lun_nome all’oggetto ritornato dalla funzione return_proc. Alla riga 13 infine chiamiamo la proc con il nome che le abbiamo assegnato alla riga precedente.
Quest’ultimo esempio, secondo me, va esaminato attentamente perché c’è dentro tutta la filosofia di Ruby. E le sue capacità. E se non rischiassi di essere considerato ripetitivo direi che questo è possibile perché in Ruby tutto è un oggetto (cit.) 😉

OPS! il widget di WordPress, ottimo, ha un piccolo baco: se nell’ultima riga c’è un _ (underscore, trattino-basso) questo non viene visualizzato.
Per questo ho inserito un # commento  alla fine di qualche script 😳

Ruby – precisazione su require

r0

Ancora una precisazione circa require e load. Non sapevo che Ruby fosse così popolare e che scatenasse curiosità anche insolite, tipo questa:

Con require e load è possibile indicare un percorso memorizzato in una variabile?

Risposta: Certo, ecco:

#!/usr/bin/ruby
# encoding: UTF-8

# lovar.rb

reqvar = '/tmp/req.rb'
require reqvar
#!/usr/bin/ruby
# encoding: UTF-8

# /tmp/rec.rb

puts __FILE__

lovar

OK, certo che /tmp non sarebbe la posizione migliore 😉
Ciò può tornare utile quando lo script deve girare su macchine diverse con sistemi operativi diversi. Per stabilire l’OS ci sono diversi modi, secondo Google, non tutti funzionanti (da me). Eccone un paio che sembrano OK:

OS

Ruby – ancora su file e directory

r9

Nel post precedente ho tralasciato parecchie cose su come gestire file e directory con Ruby. Ne è nata una discussione, credo sia il caso di approfondire l’argomento. Anche perché non ci sono cose nuove, non ancora affrontate nel corso.

Una cosa che ho dato per scontata e forse non lo è: in Ruby ci sono sempre diverse vie per fare una cosa, non so se è un bene o no, ma è la filosofia di Matz. Ancora: ci sono istruzioni che nel tempo cambiano; quello che era OK con la revisione x.y non vale più dalla z.0 in poi. Non proprio l’ideale, ma dobbiamo conviverci.

require e load

Le due istruzioni sembrano uguali, consentono entrambe di usare nello script corrente le risorse (oggetti, in Ruby tutto è un oggetto) definite al loro interno come se fossero nel file corrente. C’è però una grossa differenza: require va usato una sola volta gli oggetti vengono memorizzati mentre load è solo un modo per evitare il copia-incolla da un file a quello corrente, evitando di doverlo rifare quando si modifica l’originale; quindi può essere usato più volte nello stesso script (se uno è pasticcione, altrimenti non credo sia necessario).
Ecco un esempio minimo che usa sia require che load (loreq.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# loreq.rb

load 'lo-0.rb'

puts "sono nel main: #{__FILE__}"

require './req.rb'
puts "v = #{$v}"

load 'lo-1.rb'
puts "v2 = #{$v2}"

ci servono 3 file, 2 per load e 1 per require, eccoli:

#!/usr/bin/ruby
# encoding: UTF-8

# lo-0.rb

puts "sono nel file: #{__FILE__}"
#!/usr/bin/ruby
# encoding: UTF-8

# rec.rb

$v = 8
#!/usr/bin/ruby
# encoding: UTF-8

# lo-1.rb

$v2 = 42

Inoltre nella variabile di sistema __FILE__ viene memorizzato il nome del file corrente. Altre cose sulle directories verranno raccontate in seguito, per adesso vediamo cosa succede se lo eseguiamo:

loreq

Tutto come previsto, anche le variabili definite nei file secondari sono visibili in quello principale. Sappiamo già che quelle il cui nome inizia con $ sono globali, vero?

Ma se questo è logico per require con load la variabile dev’essere globale? Uh… ehmm, io penso che …, dai proviamo (testload.rb e incload.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# testload.rb

load 'incload.rb'
puts "nel main: prova = #{prova}"
# incload.rb

prova = 42
puts "nel file incluso: prova = #{prova}"

testload

OPS! no! load non si comporta come dovrebbe; la variabile dichiarata al suo interno non viene vista nel main. Quindi, se non è un bug che verrà corretto, non usare load ma require.

Accedere a file e directory

Non sempre i file e le risorse che ci servono sono nella stessa directory (cartella). Nessun problema, Ruby è in grado di gestire benissimo la cosa (quasi), ecco un primo esempio (path.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# path.rb

nomefile = __FILE__
puts "nomefile = #{nomefile}"
pathname = File.expand_path(nomefile)
puts "pathname = #{pathname}"
dirname = File.expand_path('../', pathname)
puts "dirname = #{dirname}"
t = dirname + '/'
puts "inutile ma dirname + '/' = #{t}"
home = File.expand_path('~')
puts "home = #{home}"

path

Tutto come previsto, il modulo File fa parte di quelli predefiniti, immediatamente disponibili, senza usare require.

L’operatore + concatena le stringhe, come già visto. Per trattare i pathnames in realtà non serve ma volendo c’è. Ovviamente ~ è definita solo per Linux. Per Windows si può usare ENV, un hash che mappa le variabili di ambiente (environment), così (hvs.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# hvs.rb

home = ENV['HOME']
puts "home = #{home}"

hvs

Attenzione: è un hash, le parentesi sono quadre 😉

FileUtils

Ci sono ancora tante cose da vedere; per esempio c’è tutto il modulo FileUtils. Che (almeno da me) non è che funzioni proprio come dovrebbe. Eccone un assaggio, maggiori informazioni, forse, in futuro (fu-pwd.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# fu-pwd.rb

require 'fileutils.rb'

fdir = FileUtils.pwd
puts "directory corrente = #{fdir}"

questo = File.join(fdir, __FILE__)
puts "questo = #{questo}"

f = File.file?(questo)
d = File.directory?(questo)

print f, " ", d, "\n"

fu-pwd

Notare che il modulo dev’essere inserito con require (riga 6) e che il nome del file è tutto minuscolo. Le funzioni contenute sono elencate qui.

Non tutte funzionano, anche perché il modulo si riferisce a una versione diversa da quella che sto utilizzando. resta indispensabile il modulo File.

Notare alla riga 11 il modo sicuro per concatenare i path e i predicati file? e directory? (righe 14 e 15). Ne esistono parecchi altri, tra i quali exists?

Per oggi basta 😀 e per un po’ basta Ruby, ci sono altre cose in coda 😉