2.43   Ruby

Ruby è un linguaggio object-oriented originato in Giappone, e qui molto diffuso, ad opera di Yukihiro Matsumoto nel 1993. Il nome Ruby si rifà a Perl, in italiano rispettivamente rubino e perla.

Ruby è un linguaggio ad oggetti, i tipi di dato sono classi e le istanziazioni sono oggetti, dotati quindi di metodi ed attributi, ad esempio i numeri appartengono alla classe Fixnum, Bignum, Complex o Float; le stringhe alla classe string. Si possono creare nuove classi da una classe esistente, ma non esiste ereditarietà multipla; tuttavia è possibile includere in una classe dei moduli, vale a dire raggruppamenti di metodi, costanti e classi. Nella   Figura 2‑3 le classi di alcuni tipi di dato con la gerarchia di appartenenza.

 

                                                                    Figura 2‑3

Il grafico di Figura 2‑3 è stato generato da Tcl/Tk (v. par. 2.53 ) richiamato da Ruby tramite[1]:

def push_itm (lst,input)

  # genera array con key nodo e livello, progressivo, padre

  subLevel = 0

  if Albero.has_key?(input[0]) then

    level = Albero[input[0]][0] + 1

  else

    level = 1

    Albero[input[0]] = [0,1,""] # presumo radice

  end 

  1.step(input.length-1,1) do |j|

     subLevel = subLevel + 1

     Albero[input[j]] =[level ,subLevel,input[0]] # presumo radice   

   end

 end

 def estrai(nodo)  # estrae ricorsivamente i figli

     Fl.printf "%2d %2d %s\n",Albero[nodo][0], Albero[nodo][1], nodo

     print "#{nodo} ";p Albero[nodo]

     Albero.each_key {|key|

       if nodo == Albero[key][2] then

          estrai(key)

        end

      }

    end

Albero = {}     # array hash dei nodi

Lista = ['Object']      # lista nodi in ordine di arrivo

push_itm(Albero,['Object','String','Numeric'])

push_itm(Albero,['Numeric','Integer','Float','Complex'])

push_itm(Albero,['Integer','Fixnum','Bignum'])

Fl = File.new("rubyobj.txt",  "w")

 

estrai("Object")

Fl.close

callTCL = %Q/wish83.exe tree.tcl rubyobj.txt Gerarchia_di_oggetti_Ruby orizzontale/

exec callTCL

Il grafico si sarebbe potuto generare da Ruby stesso mediante l'estensione nativa per Tcl/Tk (sono supportati anche altri strumenti grafici quali  GTK, OpenGL, ecc…).

Ruby ha una serie impressionante di metodi, funzioni su stringhe, su matrici, spesso ridondanti: un ciclo da 1 a 10 ( o viceversa) può essere scritto come:

9.downto(0) {|i| print i}
9.step(0, -1) {|i| print i}
10.times do |i| print i end
0.upto(9) {|i| print i}
for i in 0..9 do print i end


Dall'esempio si nota l'incongruenza della variabile di ciclo i indicata fra ||, mentre è libera come argomento di for; un'altra incongruenza
è l'utilizzo di # come commento e come valutatore di espressioni (peraltro molto utile). Anche in Ruby  esistono le scorciatoie sintattiche quali z *= b invece di z = z * b o l'assegnazione multipla, dalla cui complicazione, emerge a,b = b,a con l'effetto di scambiare i valori di a e b. Sempre a proposito di assegnazione a
= b
non genera due oggetti, ma un riferimento; per copiare un oggetto occorre usare i metodi clone o dup: b = a.dup.
Una scorciatoia, però piuttosto utile, è il delimitatore %w che permette di assegnare, in modo diretto, dei valori ad un array. Un'altra delle ridondanze
del linguaggio è: unless condizione ... che corrisponde ad if not condizione ....

Le variabili che iniziano con $ sono variabili globali; i nomi di costanti iniziano con una lettera maiuscola, tuttavia, e questa è una incongruenza del linguaggio, le costanti possono essere variate, ed essendo globali, si comportano di fatto come variabili globali.

Come alcuni altri linguaggi (AWK, in parte PL/SQL), permette di indicare esplicitamente le istruzioni iniziali e finali di un programma tramite i blocchi BEGIN {...} ed END {...}.

Fra i pregi di Ruby c'è la possibilità di scrivere espressioni condizionali (frammento 1) o utilizzare  if come funzione che fornisce un valore (frammento 2):

puts "Pericolo!!!" if radiazione > 3000     # frammento 1

a = if rand > 0.5 then "pari" else "dispari" end   # frammento 2 

Ruby ha un ottimo trattamento del parallelismo. L'esempio che segue è la simulazione di una divisione fra interi su una macchina con la sola istruzione di sottrazione (v. par. Macroassemblatori).  Il programma sorgente è stato generato con il macroprocessore M4 (v. par.Sugli Algoritmi utilizzati). Nel programma sono eseguiti in parallelo le funzioni interprete, indata (per simulare l'input) e outdata (per simulare l'output).

# RUBYWIN Platform: i586-mswin32 Version: 1.6.6 - 2001-12-26

def outdata # invia i dati in output

while $mem["Output"] == 0 do end # attende che il buffer diventi pieno

   print "Output: "; p $mem["Output"]

   $mem["Output"] = 0  # pulisce il buffer per nuovi dati

end

def indata(input) # riceve i dati in input

  while $mem["Input"] != 0 do end # attende che il buffer sia svuotato

  $mem["Input"] = input

  print "Input: "; p $mem["Input"]

end

def interprete

 while $mem["PC"] != 3 do

   pc = $mem["PC"]

   $mem[$mem[pc]] = $mem[$mem[pc]] - $mem[$mem[pc+1]]

   pc2 = $mem["PC"]    # se per caso si è modificato PC

   if $mem[$mem[pc]] < 0 then

     $mem["PC"] = pc2 - $mem[pc+2].to_i

   else

     $mem["PC"] = pc2 + 3

   end

 end 

end

# l'immagine della memoria è una matrice associativa

# gli indirizzi (chiavi della matrice) di memoria sono:

# il nome del campo o un numero progressivo a partire dal

# valore del Program Counter (PC)

$mem = {}

address = 0

IO.foreach("unaistr.mcr") {|line|         # lettura sorgente

  if line.tr(' ','').length != 1 then     # linee non vuote

     istr = line.scan(/\S+/)              # separo item

     if istr[0][0] == ':'[0] then         # nome di campo

       $mem[istr[0][1..9]] = istr[1].to_i       

       address = istr[1].to_i if istr[0] == ':PC'

     elsif istr[0] != '*' then            # commento

       istr.each do |i|                   # istruzioni

         $mem[address] = i

         address = address + 1  

       end  # do |i|

     end  # if istr ...

   end  # if line

 }

thr1 = Thread.new {indata(222);indata(44)}

thr2 = Thread.new {outdata}

thr3 = Thread.new {interprete}

# .join metodo che attende la fine thread; .join su tutti i threads li sincronizza

thr1.join; thr2.join; thr3.join;

Il risultato:

Input: 222

Input: 44

Output: 5

Ruby è un linguaggio introspettivo, ha la capacità di indicare gli oggetti che contiene ed i metodi che questi sopportano.

Nell'esempio che segue il programma determina se una formula di logica preposizionale (tesi), è derivabile da un insieme di formule date (ipotesi). Si presuppone che tesi ed ipotesi siano espresse in forma normale disgiuntiva (v. par. 3.2 ).

# RUBYWIN Platform: i586-mswin32 Version: 1.6.6 - 2001-12-26 
# - è not + è or . è and
require "reductio_sub"
def proof(hyth,thesis)
  print "hypothesis: "; p hyth
  print "thesis:     "; p thesis 
  # separa i componenti in and
  arr_hyth =hyth.split('.')
  arr_thesis = thesis.split('.')
  # separa i componenti in or
  arr_hyth.each_index  {|itm| arr_hyth[itm] = arr_hyth[itm].split('+')}
  arr_thesis.each_index {|itm| arr_thesis[itm] = arr_thesis[itm].split('+')}
  # inizio del metodo reductio
  arr_thesis.each do |itm|  # per ogni subformula della tesi
      arr_hyth_val = instantiate(arr_hyth,not_true(itm))    # assegno i valori
      $result = verify(arr_hyth_val)
      #print "  ---> result #{$result} di ";p itm
      break if $result == "F"
    end
    return $result
end            
  p proof("-a+b.-c+b.a+c","b")     # derivabile
  p proof("-a+b.-c+b.a+c","-b")    # non derivabile
  p proof("-a+b.-c+b.a+c","b+a")   # derivabile
  p proof("-a+b.-c+b.a+c","b.a")   # non derivabile
  p proof("-a+b.-c+b.a+c","x+y")   # non derivabile
  p proof("-a+b.-c+b.-a.a","b")    # inconsistente -a.a (tutto è derivabile)
  p proof("-a+b.-b+c.-c+d.a.e.-d+g.-e+c","g") # derivabile
# RUBYWIN Platform: i586-mswin32 Version: 1.6.6 - 2001-12-26 
   def not_true(arr_of_var)
    # genera una lista variabile-valore, che rendono 
    # falso or di arr_of_var 
     list = []
     (arr_of_var).each do |itm|
         list.push [itm, "F"]
         list.push ["-"+itm, "T"]
         list[-1,1][0][0] = itm[-1].chr if itm[0] == "-"[0]    
       end
     return list
   end
   def instantiate(formula,values) 
   # sostituisce in formula i valori assegnati alle variabili
     form = []
     formula.each do |frm|
       subform = []
       frm.each do |var|
         variab = var
         values.each_index do |indx|
           variab = values[indx][1] if var ==    values[indx][0]
         end
         subform << variab
       end
       form << subform
     end
     return form    
   end 
   def set_value(frm,n)
   # imposta i valori delle variabili in funzione del bit binario di n  
     list = []
     frm.length.times  do |i|
           j = n[i]        # bit in position i
           list.push [frm[i], "FT"[j,1]]
           list.push ["-"+frm[i],    "TF"[j,1]]        
           list[-1,1][0][0] = frm[i,1][0][1].chr    if frm[i][0] == "-"[0] 
     end
     return list
   end
   def verify(hyth_frm)
     $result = "F"
       hyth_frm.each_index do |frm|
         $result = "T" if hyth_frm[frm].uniq    == ["F"]    
         # return T se tutte le variabili sono F(alse)
         unless hyth_frm[frm].include? "T" 
         vrb = hyth_frm[frm].dup.delete_if {|x| x == "T"    or x == "F"}
           1.upto(2**vrb.length-1) do |n|
              arr_left = instantiate(hyth_frm[frm..hyth_frm.size],set_value(vrb,n))
              $result = verify(arr_left)
              break if $result    == "F"
           end # do |n| 
           return $result 
         end # unless hyth_frm[frm].include? "T"       
       end # do |frm|
       return $result  
   end # verify

Il risultato è quanto segue,  si noti la sesta derivazione, che il sistema riconosce verificata, ciò è dovuto alla inconsistenza delle ipotesi in quanto contengono la formula sempre falsa non a e a (-a.a).

hypothesis: "-a+b.-c+b.a+c"

thesis:     "b"

"T"

hypothesis: "-a+b.-c+b.a+c"

thesis:     "-b"

"F"

hypothesis: "-a+b.-c+b.a+c"

thesis:     "b+a"

"T"

hypothesis: "-a+b.-c+b.a+c"

thesis:     "b.a"

"F"

hypothesis: "-a+b.-c+b.a+c"

thesis:     "x+y"

"F"

hypothesis: "-a+b.-c+b.-a.a"

thesis:     "b"

"T"

hypothesis: "-a+b.-b+c.-c+d.a.e.-d+g.-e+c"

thesis:     "g"

"T"

 


[1] Il grafico si sarebbe potuto generare da Ruby stesso mediante l'estensione nativa per Tcl/Tk (sono supportati anche altri strumenti grafici quali  GTK, OpenGL, ecc…).