Archivi Categorie: Ruby

Ruby – files II

r10

Leggere byte per byte

Capita di dover leggere un file non di testo, come p.es. un file musicale o un’immagine o un eseguibile o uno zip compresso o roba simile. In questo caso occorre leggere tutti i singoli byte. Ecco come leggere il file testo.txt, questo:

Questo lo leggo
byte per byte.

Lo script e questo (lb.rb):

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

# lb.rb

File.open("testo.txt").each_byte { | byte | print byte, " " }
puts

lb

Notare il codice 10, corrispondente a \n (o meglio ^L), l’a-capo.
Invece delle graffe è naturalmente possibile usare la coppia doend, così (lb1.rb):

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

# lb.rb

File.open("testo.txt").each_byte do | byte |
    print byte, " "
end
puts

Ruby, al solito, consente di usare il modo che si preferisce.

Leggere un carattere per volta.

Dalla versione 1.9 (potete verificare quella installata con ruby -v) è possibile leggere un byte per volta visualizzando i caratteri invece del codice ASCII (l1b.rb):

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

# l1b.rb

File.open("testo.txt").each_char { | byte | print byte, " " }
puts

l1b

Cambiare nome al file

Semplicissimo, c’è rename, ecco come rinominare vecchio.txt in nuovo.txt (ren.rb):

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

# ren.rb

File.rename "vecchio.txt", "nuovo.txt"

ren

Attenzione:come al solito non sono necessarie le parentesi ma se si mettono c’è un bug (probabilmente):

quest’istruzione funziona:
File.rename("vecchio.txt", "nuovo.txt")

questa invece da errore:
File.rename ("vecchio.txt", "nuovo.txt")

Naturalmente con Ruby è possibile copiare e cancellare files, creare directories (cartelle) e operazioni simili; se serve ne faccio un post.

Trovare la posizione in un file

Con pos è possibile determinare la posizione nel file; usiamo il file di testo testo.txt di prima vediamo come (posiz.rb):

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

# posiz.rb

f = File.open "testo.txt"
puts "Inizio: #{f.pos}"
f.gets
puts "Dopo aver letto la prima riga: #{f.pos}"
f.gets
puts "Dopo aver letto la seconda riga: #{f.pos}"

posiz

Cioè pos ci dice quanti bytes sono stati letti, o meglio, a quanti byte dall’inizio del file, è il puntatore di lettura. È così possibile spostarsi all’interno del file leggerne solo una parte, esempio (parte.rb):

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

# parte.rb

f = File.open "modif.txt"
f.pos = 21
r = f.gets
puts "ho letto: #{r}"

parte

Scrivere in un file

Nel post precedente avevamo visto come scrivere in un file riga per riga.
Ma Ruby ci consente di inserire blocchi multilinea tutto in una volta, eccone un esempio (wb.rb):

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

# wb.rb

File.open "out.txt", "w" do |f|
    testo_da_scrivere = <<TESTO_DA_SCRIVERE
Questo è il testo che viene
scritto nel file "out.txt".

Come si vede sono più righe
anche con righe vuote e
tutti i caratteri che voglio,
senza bisogno di delimitarlo
con apici o virgolette.
    E ci sono anche i rientri.
fine.
TESTO_DA_SCRIVERE

    f.puts testo_da_scrivere
end

wb

Come si vede il blocco di testo è quello contenuto all’interno dell’identificatore, definito con << e chiuso ripetendolo. Ho usato TESTO_DA_SCRIVERE ma può essere quel che si vuole; per convenzione si usa scriverlo tutto in maiuscolo.

Modalità di apertura

Nell’esempio precedente il file è stato aperto in scrittura, vedi il parametro "w" alla riga 6. per la lettura si usa "r" che essendo di default viene spesso omessa. Ma ce ne sono altre:

  • r : lettura, il puntatore è all’inizio del file;
  • r+ : sono possibili sia la lettura che la scrittura, il puntatore è all’inizio del file;
  • w : solo scrittura, se il file non esiste viene creato, se esiste viene sovrascritto;
  • w+ : come r+ con la differenza che se non esiste viene creato;
  • a : append-mode. Il puntatore viene posizionato alla fine del file per cui è possibile scrivere testo aggiuntivo senza perdere il contenuto esistente;
  • a+ : come a con la possibilità di lettura;
  • b : binary mode, per leggere i file non di testo.

Appendere testo a un file

Usando out.txt (creato prima) vediamo come possiamo inserire l’ora, con la modalità a appena descritta (ora.rb):

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

# ora.rb

f = File.open "out.txt", "a"
f.puts "\nData odierna:#{Time.now.to_s}\n"

ora

Memorizzare oggetti in un file

Finora abbiamo visto come mettere testo in un file, adesso vediamo come è possibile mettere oggetti o istanze a classi nei files.

Pstore
Pstore è un formato binario con il quale è possibile registrare quasi tutto. Vediamo come possiamo registrare una classe; per prima cosa creiamo il file della classe (quad_class.rb):

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

# quad_class.rb

class Quad #quadrato
    attr_accessor :lun_lato

    def initialize lun_lato = 0
        @lun_lato = lun_lato
    end

    def area
        @lun_lato * @lun_lato
    end

    def perimetro
        @lun_lato * 4
    end
end

Adesso creiamo lo script pstore_write.rb:

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

# pstore_write.rb

require './quad_class.rb'

s1 = Quad.new
s1.lun_lato = 4
s2 = Quad.new
s2.lun_lato = 7

require 'pstore'
store = PStore.new('quadrati')
store.transaction do
    store[:quad] ||= Array.new
    store[:quad] << s1
    store[:quad] << s2
end

Esaminiamo il file: require './quad_class.rb' alla riga 6 include il file quad_class.rb dentro lo script, come se l’avessimo scritto qui; in questo modo lo script risulta più piccolo e il file della classe può essere riciclato più volte.

creiamo poi due quadrati s1 e s2, righe 8-11.

Alla riga 13 includiamo il file pstore (di sistema, cioè fornito con Ruby) in modo da poterne usare il formato.
Con la riga 14 creiamo il file quadrati e con le righe 15-19 ci scriviamo dentro i quadrati s1 e s2 creati prima nell’array quad.
||= significa che l’array non è necessario crearlo se già esistente.
A questo punto possiamo eseguire lo script:

pstore_write

Come si vede viene creato il file quadrati, 41 byte. Il file è binario e non riuscite a leggerlo con un file di testo; se siete curiosi potete usare un edito esadecimale, ecco cosa ottenete:

click

click

È giunto il momento di creare lo script per leggere i dati memorizzati in quadrati, così (pstore_read.rb):

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

# pstore_read.rb

require './quad_class.rb'
require 'pstore'

store = PStore.new('quadrati')
quad_m = []
store.transaction do
    quad_m = store[:quad]
end

quad_m.each do |quad|
    puts "Lato = #{quad.lun_lato}"
    puts "Area = #{quad.area}"
    puts "Perimetro = #{quad.perimetro}"
    puts
end

pstore_read

Come si vede i due quadrati vengono visualizzati. I lati vengono ritrovati dal file quadrati, con le istruzioni delle righe 9-13.

In particolare viene creato l’array quad_m vuoto (riga 10) riempito con il ciclo delle righe 11-13.
Il ciclo delle righe 15-20 visualizza i quadrati. Notare che il calcolo di area e perimetro vengono svolti solo in questa fase e scritti direttamente senza memorizzarli in una variabile.

Il manuale che sto seguendo (Karthikeyan) propone anche un altro metodo di immagazzinamento dei dati; è solo una variante del precedente. Non lo riporto perché non aggiunge niente di nuovo.

E poi il post è già troppo lungo 😉

Ruby – files I

r9Finora abbiamo sempre letto e scritto sul terminale. Ma oggi si cambia, usiamo i file.
Intanto sappiamo tutti reindirizzare inpuut e output con < e > e usare la pipe | per concatenare i programmi, vero? Se ci sono dubbi e lacune lasciate un commento e faccio un post tutto per quello (che è uno dei miei argomenti preferiti).

Leggere un file

Ecco un file di testo, questo (dati.txt):

Questo è il file <code>dati.txt</code>

Viene usato come esempio
per spiegare la gestione dei files
da parte di Ruby.

Per leggerlo e visualizzarlo nel terminale usiamo questo script (leggi0.rb):

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

# leggi0.rb

txt = File.open("dati.txt").readlines
puts txt

leggi0

La riga 6 apre il file con open e ne legge il contenuto con readlines.

Anche se il file contiene lettere accentate non è indispensabile la riga 2, è però bene metterla sempre, per il caso di dover immettere dati da tastiera.

In questo modo abbiamo letto il contenuto del file tutto in una volta. È anche possibile separare le singole righe. In alternativa vediamo come si fa a leggerle già separatte. N.B.: non sto seguendo Karthikeyan, mi sembra poco chiaro.

#!/usr/bin/ruby
<pre># encoding: UTF-8

# leggi0.rb

testo = []
File.open("dati.txt").each do |riga|
    testo << riga
end

num_righe = testo.length
0.upto num_righe - 1 do |n|
    puts "#{n} - #{testo[n]}"
end

leggi1

Nella riga 6 creiamo il vettore testo, vuoto che riempiamo nelle due righe successive. Il numero di righe lette lo ritroviamo, corrisponde alla lunghezza del vettore, riga 11. Di seguito scriviamo una riga per volta, preceduta dal suo numero d’ordine. Tutte cose già viste, vero? Ripassare 🙂

Però, ripensandoci, che ne dite di questa soluzione (leggi2.rb):

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

# leggi2.rb

testo = File.open("dati.txt").readlines
num_righe = testo.length
0.upto num_righe - 1 do |n|
    puts "#{n} - #{testo[n]}"
end

Si legge tutto in una volta è già un array. Quale soluzione usare? dipende: può essere utile operare subito sulla riga letta e allora si usa la prima soluzione, altrimenti la seconda, se il file non è troppo grande.

Finora abbiamo usato open ma esiste un altro modo, più generale, usando new (leggi3.rb):

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

# leggi3.rb

f = File.new("dati.txt", "r")
testo = []
while (riga = f.gets)
    testo << riga
end
f.close

num_righe = testo.length
0.upto num_righe - 1 do |n|
    puts "#{n} - #{testo[n]}"
end

Nella riga 6 apriamo il file in lettura (la "r" del secondo parametro) ottenendo il descrittore f.

Il ciclo alle righe 8-10 legge una riga per volta che mette nell’array testo.
Importante: chiudere il file quando abbiamo finito (riga 11).

Scrivere su un file

L’operazione complementare alla lettura di un file è, ovviamente, la sua scrittura. Ecco un esempio (scrivi0.rb):

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

# scrivi0.rb

f = File.new("out.txt", "w")
f.puts("Adesso questo diventa\nil testo del file\nout.txt")
risposta = 7 * 3 * 2
f.puts("\nLa risposta è, ovviamente, pari a #{risposta}")
f.close

scrivi0

Il file viene aperto con "w" per “write”. Poi possiamo scriverci dentro cosa vogliamo. Ricordarsi di chiuderlo.

Definire il fine-linea (l’a-capo)

Linux usa \n come sequenza di fine linea (è un solo carattere, il ^L) mentre Windows (con il Blocco note, per esempio) considera come fine linea la sequenza \r\n (^M^L, due caratteri). Peraltro esistono editor meno rozzi di Blocco note, come p.es. Notepad2 (free, cercatelo nel Web). In ogni caso Ruby consente di definire come fine-linea la sequenza che vogliamo.
Vediamo un esempio pratico, anche se non troppo sensato: usiamo questo file (pv-sep.txt):

file usato per visualizzare;il fine-linea
personalizzato;nel nostro caso è il punto-virgola;;
due di seguito valgono una riga vuota.

Ecco il risultato con questo script (sep-linea.rb):

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

# sep-linea.rb

txt = File.open("pv-sep.txt").each(';') do |riga|
    puts riga
end

sep-linea

OK, uno schifo, ma con i files di Windows (Blocco note|Notepas) funziona.

Siccome ci sono ancora parecchie cose da raccontare facciamo pausa,  qui 😀

Ruby date e tempo

r8
La data e l’ora del computer è disponibile, ecco:

t0

OK? Come si vede dipende dalle impostazioni locali, ma queste sono visualizzate (io sono a +2:00 rispetto a UTC).
Siccome in Ruby tutto è un oggetto Time.now crea una nuova istanza a Time:

t1

È possibile estrarre i singoli elementi dall’oggetto, ecco per la data:

t2

wday si riferisce al nome del giorno (weekday):

0   1   2   3   4   5   6
dom lun mar mer gio ven sab

e yday il numero di giorni dall’inizio dell’anno.

Per l’ora abbiamo:

t3

dove usec è il numero di microsecondi relativo alla creazione dell’istanza; torna utile per determinare intervalli di tempo. Notare che il simbolo per “micro” del SI (sistema internazionale) sarebbe μ ma quando questo non è disponibile si può usare u.
zone è il nome del fuso orario, quando disponibile, nel mio caso (Central Europe Saving Time), isdst ci dice se l’ora legale è attiva e utc? se stiamo usando l’ora di UTC.

t4

localtime restituisce l’ora locale, gmtime quella di UTC. Notare che GMT (Greenwich Mean Time) è il vecchio nome di UTC e non ci sono due “t” come verrebbe da pensare (Matz pasticcione!).

t5

getlocal è sinonimo di localtime come getutc di gmtime.
ctime è il formato del C, tipico di Unix. Attenzione che suppone errando che il tempo interno del computer sia UTC.
In un giorno ci sono 60 * 60 * 24 = 86400 secondi per cui ieri t avrebbe restituito l’ultima stringa.

Vediamo come possiamo calcolare l’intervallo di tempo tra due date, diciamo tra il 25 febbraio del 2010 e il 1° maggio dello stesso anno:

t6

Cioè abbiamo creato due oggetti per le rispettive date, fatto la loro differenza ottenendo il numero di secondi e diviso questo per il numero di secondi al giorno. Il risultato è approssimato ma rimediamo prima di subito con round. Non ci siamo dimenticati che _ è l’ultimo valore restituito dall’interprete, vero?; e che questo è disponibile in irb (di cui il nostro irq è un alias).

Volete sapere quanto siete vecchi? Ecco old.rb:

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

# old.rb

print "data di nascita (GG MM AAAA): "
bday = gets.chop
giorno, mese, anno = bday.split(' ')
ora = Time.now
nascita = Time.local(anno, mese, giorno)
seconds = ora - nascita
days = (seconds / 86400).round
puts "Sei vecchio di #{days} giorni"

old

OK, sono vecchio. Notare che

nascita = Time.local(anno, mese, giorno)

si può anche scrivere

t7

ma non

t8

Ruby – moduli e mixin

r7

Un modulo è una raccolta di metodi (funzioni) e costanti per fare qualcosa. Quello che in altri linguaggi namespace o package o module (sì, in Python). Quando si vuole usare un modulo in Ruby basta includerlo nel programma.
Vediamone un esempio pratico di creazione e uso.

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

# es_mod.rb

module Star
    def line
        puts '*' * 20
    end
end

module Dollar
    def line
        puts '$' * 20
    end
end

include Star
line
include Dollar
line

es_mod

Vediamo che il modulo inizia con la keyword module (righe 6 e 12) e finisce, al solito, con end. Viene richiamato con include <nome del modulo>, righe 18 e 20.

Nell’esempio vediamo che la funzione line che viene eseguita  quella dell’ultimo modulo incluso.

Facciamo una modifica allo script precedente: togliamo le righe con le istruzioni di inclusione dei moduli:

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

# es_mod_err.rb

module Star
    def line
        puts '*' * 20
    end
end

module Dollar
    def line
        puts '$' * 20
    end
end

line

es_mod_err

Come si vede la funzione line non viene vista.

Possiamo avere anche moduli senza funzioni, solo codice. Poi proviamo a eseguirlo (mod_sf.rb):

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

# es_mod_sf.rb

module Uno
    puts "uno"
end

module Due
    puts "due"
end

mod_sf

Vediamo che in questo caso il modulo viene eseguito.

Sono da verificare almeno altri due casi, eccoli di seguito.

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

# mod_c-f.rb

module Uno
    puts "uno"

    def func
        puts "*** func"
    end
end

module Due
    puts "due"
end

Rispetto allo script precedente è stata inserita la funzione func nel modulo Uno. Provate a eseguirlo e vedrete che l’output è esattamente come quello del caso precedente, la funzione non essendo chiamata non viene eseguita. Cosa succede se invece questi moduli venissero inclusi in un main, come nello script seguente (mod_m.rb):

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

# mod_m.rb

module Uno
    puts "uno"

    def func
        puts "*** func"
    end
end

module Due
    puts "due"
end

include Uno
func

mod_m

Sorpresa! O no? In realtà il codice del modulo non incluso in funzioni serve per definire, inizializzare e simili.

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

# mod_m2.rb

module Uno
    puts "uno"

    $pi = 3.14159265
    def func
        puts "*** func"
        puts "Uno  - pi = #{$pi}"
    end
end

module Due
    puts "due"
end

include Uno
func
puts "Main - pi = #{$pi}"

mod_m2

Chiamare funzioni senza usare include

È possibile chiamare le funzioni contenute in un modulo senza usare la keyword include, tramite l’operatore ::. Modifichiamo il primo script in questo modo (es_mod2.rb):

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

# es_mod2.rb

module Star
    def Star.line
        puts '*' * 20
    end
end

module Dollar
    def Dollar.line
        puts '$' * 20
    end
end

Star::line
Dollar::line

es_mod2

Notare che per chiamare le funzioni usiamo :: (righe 18 e 19) e che per definirle usiamo nome-del-modulo.nome-funzione (righe 7 e 13).

I due metodi (usare include e ::) possono coesistere. Poi però sta a voi districarvi. Quando ero piccolo mi facevano sempre fare il debug, era una punizione (il mondo ce l’aveva con me già allora).

Classi nei moduli

Vediamo adesso cosa succede quando abbiamo classi nei moduli (mc.rb):

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

# mc.rb

module Operaio
    class Lavoro
        def cosa_stai_facendo?
            puts "Io eseguo"
        end
    end
end

module Dirigente
    class Lavoro
        def cosa_stai_facendo?
            puts "Io dirigo"
        end
    end
end

r1 = Dirigente::Lavoro.new
r2 = Operaio::Lavoro.new
r1.cosa_stai_facendo?
r2.cosa_stai_facendo?

mc

Se avete seguito fin qui non dovreste essere sorpresi: viene usata la funzione in nome-modulo::nome-classe.

Piccolo problema logico qui: io seguo abbastanza fedelmente Karthikeyan e ho usato per la funzione un nome che termina con ?. in realtà sarebbe buona norma riservare questi nomi ai predicati, funzioni che restituiscono un valore booleano True o False. Prima o poi dovrete fare il debug, ricordatevelo.

Mixin

In Ruby è che non c’è l’ereditarietà multipla e allora i mixin la simulano: permettono di avere un mix di classi per avere accesso diretto ai loro metodi, è cioè possibile mischiare il codice in diversi moduli, ottenendo i mixin.
Ecco un caso in cui non seguo l’ottimo Karthikeyan. Perché fare un esempio breve dell’uso dei mixin non è facile, anzi…

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

# es_mod2.rb

module InLettere
    def inlettere
        if @value == 1
            "Uno"
        elsif @value == 2
            "Due"
        elsif @value == 3
            "Tre"
        end
    end
end

module Math
    def somma(val_one, val_two)
        BigInteger.new(val_one + val_two)
    end
end

class Number
    def intValue
        @value
    end
end

class BigInteger < Number

    include InLettere
     extend Math

    def initialize(value)
        @value = value
    end
end

bigint1 = BigInteger.new 10
puts "dieci vale #{bigint1.intValue}"

bigint2 = BigInteger.somma 4, -2
puts "4 - 2 = #{bigint2.intValue}"
puts "vale a dire #{bigint2.inlettere}"

module CurrencyFormatter
    def format
        "€ *#{@value}*"
    end
end

bigint2.extend CurrencyFormatter
puts "la banca dice #{bigint2.format}"

es-mx

Questo l’ho trovato googlando, non è granché ma illustra l’estensione di classi e c’è un mixin alla riga 32.

Ha un altro pregio: con Ruby è molto facile produrre codice aggrovigliato, come in questo caso.
Avvisati, nèh 😉

Ruby – classi e oggetti V

r6Polimorfismo

Negli oggetti polimorfismo si riferisce a quei metodi che possono operare con diversi tipi di dati, per esempio l’operatore +

p0

applicato a stringhe le concatena, con i numeri li somma.

Lo stesso capita con *

p1

Comportamento ancora diverso per il metodo length:

p2

per la stringa ritorna il numero di caratteri, per l’array il numero di elementi. Cioè in Ruby i metodi si comportano in funzione dei tipi di oggetto cui sono riferiti, come nel mondo reale.

Costanti di classe

In Ruby possiamo avere costanti contenute in una classe, esempio (cc.rb):

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

# cc.rb

class Qualcosa
    Cost = 42

    #altri metodi della classe
end

puts Qualcosa::Cost

cc

Come si vede per accedere alla costante contenuta nella classe useremo nome_classe::nome_costante. Se tentassimo di usare la solita sintassi otterremmo un errore, esempio (errcc.rb):

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

# errcc.rb

# ATTENZIONE: CODICE ERRATO

class Qualcosa
    Cost = 42

    #altri metodi della classe
end

s = Qualcosa.new #oggetto di tipo Qualcosa
puts s.Cost

errcc

Si può risolvere quest’errore definendo un metodo che restituisce la costante; questo metodo può avere lo stesso nome (cosa che personalmente mi piace poco). Ecco okcc.rb:

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

# okcc.rb

class Qualcosa
    Cost = 42

    def Cost
        Cost
    end
end

s = Qualcosa.new #oggetto di tipo Qualcosa
puts s.Cost #chiama il metodo, quindi è OK

okcc

In alternativa è sempre possibile usare l’operatore ::, come visto sopra. La mia versione preferita.

Pausa perché la prossima volta argomento completamente nuovo 😉

Ruby – classi e oggetti IV

r5

Riflessione (reflection).

Ecco una parola difficile, per un concetto abbastanza particolare che forse in questa esposizione elementare si potrebbe anche saltare.
Intanto riporto la definizione presa da Wikipedia:

In informatica, la riflessione o reflection è la capacità di un programma di eseguire elaborazioni che hanno per oggetto il programma stesso, e in particolare la struttura del suo codice sorgente.

Per queste cose Ruby è OK, tutto è un oggetto e è stato influenzato anche dal Lisp, l’ideale in questo campo. L’articolo di Wikipedia è da leggere, altrimenti il rischio di non capire è forte. Ho anche provato a riassumerlo, tentativo non riuscito, leggetelo di là.

OK, un accenno, vediamo, con l’interprete interattivo:

r0

la risposta è molto più lunga, quello che voglio illustrare è che methods ritorna l’elenco dei metodi della stringa; perché a è una stringa, vero?

r1

Proviamo allora a definire una classe, e vedere come funziona per essa la riflessione (refl.rb):

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

# refl.rb

class Qualcheclasse
    attr_accessor :a, :b

    private
    def private_method
    end

    protected
    def protected_method
    end

    public
    def public_method
    end
end

qualcosa = Qualcheclasse.new
qualcosa.a = 'a'
qualcosa.b = 123

puts "qualcosa appartiene a #{qualcosa.class}"
puts
puts "qualcosa ha le seguenti variabili d'istanza:"
puts qualcosa.instance_variables.join(', ')
puts
puts "qualcosa ha i seguenti metodi:"
puts qualcosa.methods.join(', ')
puts
puts "qualcosa ha i seguenti metodi pubblici:"
puts qualcosa.public_methods.join(', ')
puts
puts "qualcosa ha i seguenti metodi privati:"
puts qualcosa.private_methods.join(', ')
puts
puts "qualcosa ha i seguenti metodi protetti:"
puts qualcosa.protected_methods.join(', ')

Eseguendolo con il comando ruby refl.rb otteniamo un output kilometrico, questo:

qualcosa appartiene a Qualcheclasse

qualcosa ha le seguenti variabili d’istanza:
@a, @b

qualcosa ha i seguenti metodi:
a, a=, b, b=, protected_method, public_method, nil?, ===, =~, !~, eql?, hash, , class, singleton_class, clone, dup, initialize_dup, initialize_clone, taint, tainted?, untaint, untrust, untrusted?, trust, freeze, frozen?, to_s, inspect, methods, singleton_methods, protected_methods, private_methods, public_methods, instance_variables, instance_variable_get, instance_variable_set, instance_variable_defined?, instance_of?, kind_of?, is_a?, tap, send, public_send, respond_to?, respond_to_missing?, extend, display, method, define_singleton_method, object_id, to_enum, enum_for, ==, equal?, !, !=, instance_eval, instance_exec, __send__, __id__

qualcosa ha i seguenti metodi pubblici:
a, a=, b, b=, public_method, nil?, ===, =~, !~, eql?, hash, , class, singleton_class, clone, dup, initialize_dup, initialize_clone, taint, tainted?, untaint, untrust, untrusted?, trust, freeze, frozen?, to_s, inspect, methods, singleton_methods, protected_methods, private_methods, public_methods, instance_variables, instance_variable_get, instance_variable_set, instance_variable_defined?, instance_of?, kind_of?, is_a?, tap, send, public_send, respond_to?, respond_to_missing?, extend, display, method, define_singleton_method, object_id, to_enum, enum_for, ==, equal?, !, !=, instance_eval, instance_exec, __send__, __id__

qualcosa ha i seguenti metodi privati:
private_method, initialize_copy, remove_instance_variable, sprintf, format, Integer, Float, String, Array, warn, raise, fail, global_variables, __method__, __callee__, eval, local_variables, iterator?, block_given?, catch, throw, loop, caller, trace_var, untrace_var, at_exit, syscall, open, printf, print, putc, puts, gets, readline, select, readlines, `, p, test, srand, rand, trap, exec, fork, exit!, system, spawn, sleep, exit, abort, load, require, require_relative, autoload, autoload?, proc, lambda, binding, set_trace_func, Rational, Complex, gem, gem_original_require, initialize, singleton_method_added, singleton_method_removed, singleton_method_undefined, method_missing

qualcosa ha i seguenti metodi protetti:
protected_method

OK? Notare l’uso di join (che concatena le stringhe) per l’output. Forse della riflessione dovremo parlarne ancora.

Incapsulazione

capsula

Avete presente quelle medicine contenute in una capsula come quelle in figura? Ecco un concetto simile esiste nella programmazione OOP, quindi in Ruby. L’incapsulazione serve a nascondere i dettagli quando questi non sono richiesti, facilitando la scrittura e la manutenzione del codice. Esattamente come la vostra auto: quello che vi serve è la chiave d’accensione e il volante, non v’interessa cosa c’è sotto il cofano, almeno non dovrebbe.

Ecco un esempio (incap.rb):

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

# incap.rb

class Human
    attr_reader :firstname, :lastname

    def name=(name)
        @firstname, @lastname = name.split
    end
end

me = Human.new
me.name = "Juhan vanJuhan"
puts "   Nome: #{me.firstname}"
puts "Cognome: #{me.lastname}"

incap

Notare che in questo modo firstname e lastname possono essere assegnati solo attraverso il metodo name di Human. Scrivere firstname = "Juhan" darebbe un errore.

Notare inoltre che lo script non funziona con i carabinieri e per il Poli di Torino (e probabilmente anche per altre organizzazioni). Non funziona neanche con i nomi cinesi, giapponesi e ungheresi. E quelli composti. Insomma dovreste riscriverlo 😉 meglio.

Ruby – classi e oggetti III

r4Continuiamo l’esame delle classi, abbiamo ancora parecchie cose da vedere, per esempio adesso parliamo di ridefinizione dei metodi, che sarebbe poi quello che si chiama correntemente overriding methods.

Ridefinizione di metodi

Abbiamo visto che per l’ereditarietà se deriviamo una classe B da una preesistente classe A, B eredita i metodi di A. Quando creiamo un’istanza di tipo B e chiamiamo un suo metodo Ruby verifica se tale metodo è definito per B e lo esegue; se questo metodo manca Ruby lo cerca tra i metodi di A e se lo trova lo esegue; diversamente solleva un errore (NoMethodFound).

Ecco un esempio:

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

# om0.rb

class A
    def appartiene_a
        puts "Io appartengo alla classe A"
    end

    def altro_metodo
        puts "Ecco un altro metodo della classe A"
    end
end

class B < A
    def altro_metodo
        puts "Ecco un altro metodo della classe B"
    end
end

a = A.new
b = B.new
a.appartiene_a
a.altro_metodo
b.appartiene_a
b.altro_metodo

om0

L’oggetto a esegue i suoi metodi appartengo_a e altro_metodo. L’oggetto b della classe derivata da a esegue il metodo appartengo_a, ereditato e per il metodo altro_metodo esegue il proprio perché ridefinito.

Ma c’è di più. Sappiamo che in Ruby tutto è un oggetto, lanciamo allora la versione interpretata (nota per i nuovi: irq è il nostro alias (o equivalente) per irb --simple-prompt):

om1

come si vede il metodo class ci dice a che classe appartiene l’oggetto. In particolare gli interi appartengono alla classe Fixnum. Ridefiniamo il metodo + per Fixnum. Per intanto vediamo che + somma, come nell’esempio qui sotto

om2

ma adesso lo ridefiniamo:

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

# om3.rb

class Fixnum
    def + a
        42
    end
end

puts 3 + 5
puts 7 + 12

om3

OPS! niente più somme per gli interi, sì perché gli abbiamo detto di restituire 42 indipendentemente da cosa gli passiamo.

C’è una cosa particolare da notare: Fixnum è una delle classi fondamentali di Ruby (una delle core classes) ma, a differenza di altri linguaggi, Ruby ci consente di modificarla. In genere questa caratteristica viene vista come una cosa pericolosa (ve ne accorgerete quando dovrete fare il debug di un programma con classi altamente nidificate). Per cui ogni volta che si ridefinisce un metodo occorre fare attenzione, prevedendone le conseguenze. Ma è anche la cosa che rende Ruby sexy per tanti, specialmente i lispisti.

La funzione super

Esaminiamo il seguente programma:

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

# om4.rb

class Rettangolo
    def set_dimension lun, larg
        @lun, @larg = lun, larg
    end

    def area
        @lun * @larg
    end
end

class Quadrato < Rettangolo
    def set_dimension lato
        super lato, lato
    end
end

quad =Quadrato.new
lato = 8
quad.set_dimension lato
puts "Lato: #{lato}, Area:#{quad.area}"

om4

La cosa nuova qui è il metodo set_dimension di Quadrato (riga 18) che chiama il suo antenato con la keyword super, passandogli lato due volte per lun e larg.

Estendere una classe

Ruby consente di estendere una classe, non importa se scritta da te o di quelle di base, per aggiungere altri metodi. Vediamo come estendere la classe Fixnum per gestire gli intervalli di tempo.

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

# om5.rb

class Fixnum
    def minute
        to_s.to_i * 60
    end

    def hour
        to_s.to_i.minute * 60
    end

    def day
        to_s.to_i.hour * 24
    end

    def week
        to_s.to_i.day * 7
    end
end

t = Time.now
puts "Adesso:            #{t}"
puts "Tra due settimane: #{t + 2.week}"

om5

OK? Fixnum non sa come gestire la settimana di Time, ma basta dirglielo. Vale quanto detto in precedenza: potete fare quello che volete, con rischio bug.

😉 🙂 😀

Ruby – classi e oggetti II

r3

Oggi si continua con gli oggetti.

Metodi privati

I metodi (funzioni in una classe) sono per default pubblici, cioè possono essere utilizzati al di fuori della classe, in qualsiasi parte dello script. Se si desidera che un metodo possa essere chiamato solo all’interno della classe in cui è definito bisogna dichiararlo privato, esempio:

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

# pm0.rb

class Human
    attr_accessor :name, :age

    def dimmi_di_te
        puts "Ciao, sono #{@name}. E ho #{@age} anni."
    end

    private
    def dimmi_un_segreto
        puts "Non sono umano, sono un robot\n" +
             "costruito dalla Sirius Cybernetics Corporation."
    end
end

h = Human.new
h.name = "Marvin"
h.age = 31419265
h.dimmi_di_te
h.dimmi_un_segreto #questo non funziona

pm0

Ecco, non funziona per via della chiamata a dimmi_un_segreto (riga 24) dichiarato private (riga 13).

Conviene dichiarare privati certi metodi per semplificare la programmazione; vediamo come modificare il programma precedente e accedere al metodo dimmi_un_segreto:

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

# pm0.rb

class Human
    attr_accessor :name, :age

    def dimmi_di_te
        puts "Ciao, sono #{@name}. E ho #{@age} anni."
    end

    def confessa
        dimmi_un_segreto
    end

    private
    def dimmi_un_segreto
        puts "Non sono umano, sono un robot\n" +
             "costruito dalla Sirius Cybernetics Corporation."
    end
end

h = Human.new
h.name = "Marvin"
h.age = 31419265
h.dimmi_di_te
h.confessa

pm1

OK, il metodo confessa (riga 28) è pubblico e possiamo usarlo nel main. Questo metodo chiama (riga 14) dimmi_un_segreto, metodo privato, ma può farlo perché è all’interno della classe Human, comune a entrambi i metodi. Con questo script così semplice sembra non ci sia nessun vantaggio ma nei casi reali le cose cambiano, eccome!

Variabili di classe e metodi

Finora abbiamo creato una classe e abbiamo visto che questa può avere attributi (come name e age) e funzioni (metodi) che possono essere chiamati da istanze alla classe (gli oggetti, h negli esempi precedenti).
Vediamo ora come chiamare una funzione senza dichiarare una variabile di quella classe.
Nel seguente esempio creiamo la classe Robot e usiamo la variabile @@robot_count che memorizza il numero di robot che sono stati creati. Siccome è una variabile di classe (class variable) il nome deve iniziare con @@.

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

# vm0.rb

class Robot
    def initialize
        if defined?(@@robot_count)
            @@robot_count += 1
        else
            @@robot_count = 1
        end
    end

    def self.robots_created
        @@robot_count
    end
end

r1 = Robot.new
r2 = Robot.new
puts "Creati #{Robot.robots_created} robot"

r3, r4, r5 = Robot.new, Robot.new, Robot.new
puts "Creati #{Robot.robots_created} robot"

vm0

Ho creato la funzione robot_created che ritorna il numero di robot che sono stati creati; notare che è scritta come self.robots_created dove la keyword self indica che la funzione può essere chiamata senza indicare l’oggetto a cui si riferisce, è cioè una variabile di classe.

Inoltre defined? (riga 8) è un predicato (funzione booleana) che verifica se la variabile è già esistente.
Karthikeyan, la guida che sto seguendo per questi post su Ruby, fa un esempio per far vedere un errore che salto, il tutto per dire che le variabili di classe non devono essere dichiarate come attr_reader.

Ereditarietà

Come nel mondo reale nella programmazione c’è l’eredità, una classe può avere proprietà prese (ereditate) da un’altra, con variazioni piccole o grandi.
Vediamo un esempio: i quadrati sono rettangoli con tutti i lati uguali, allora ecco:

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

# rq.rb

class Rettangolo
    attr_accessor :lunghezza, :larghezza

    def initialize lunghezza, larghezza
        @lunghezza = lunghezza
        @larghezza = larghezza
    end

    def area
        @lunghezza * @larghezza
    end

    def perimetro
        2 * (@lunghezza + @larghezza)
    end
end

class Quadrato < Rettangolo
    def initialize lunghezza
        @larghezza = @lunghezza = lunghezza
    end
end

q = Quadrato.new 5
puts "Il perimetro del quadrato q è #{q.perimetro}"
r = Rettangolo.new 3, 5
puts "L'area del rettangolo r è #{r.area}"

rq

Abbiamo definito la classe Rettangolo con due attributi, lunghezza e larghezza. che usiamo per i metodi area e perimetro di questa classe. Abbiamo poi definito la classe Quadrato che eredita le proprietà di Rettangolo (con < Rettangolo, riga 23). Per questa classe dobbiamo solo dire che lunghezza e larghezza sono uguali (riga 25), il calcolo di area e perimetro restano quelli ereditati e non dobbiamo riscriverli.

(Nota: Karthikeyan mette altri metodi, non usati che ho tolto per rendere l’esempio più semplice).

Siccome ci sono ancora tante cose da raccontare per oggi basta, continuiamo prossimamente 😉 🙂

Ruby – caratteri Unicode

r11

Recentemente mi hanno fatto notare che il terminale di Windows non supporta i caratteri Unicode con Python. O, meglio, bisogna dirgli di farlo, ho raccontato quello che ho trovato qui e qui.

uni2013

E Ruby? Vediamo, per adesso su Linux:

#!/usr/bin/ruby
# u0.rb

puts "provo le lettere accentate"
puts "àèéìòù ÀÈÉÌÒÙ ç € ü Ü"
puts "OK?"

u0

OPS! no, liscio no. Ma niente panico: quando il gioco si fa duro (cit.) viene l’ora di googlare (eh-eh-eh! non (cit.)).

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

# u1.rb

puts "provo le lettere accentate"
puts "àèéìòù ÀÈÉÌÒÙ ç € ü Ü"
puts "OK?"

u1

OK! basta inserire # encoding: UTF-8 nella seconda riga, caso risolto. E per completezza, la dritta viene dal solito Stack Overflow, qui.

Pare (nel senso che non ho verificato) che sia valido solo dalla versione 1.9.

Chissà con Windows? Uh, provo:

wu

D’oh! (cit.) funziona esattamente come per Linux, a eccezione della nota avversione per l’euro.

Ruby – classi e oggetti I

r2

Oggi comincio con un argomento lungo, e un po’ più impegnativo del solito, avete presente “quando il gioco si fa duro…” (cit.).
Ma, dai! non è difficile, vedrete.
Cominciamo con definire il concetto di classe. Una classe è un’unione di variabili e funzioni raccolte sotto un nome comune.
Vediamone subito un esempio, la classe Quadrato.
Comincio con creare una classe vuota, così:

class Quadrato
end

La parola class indica che stiamo definendo una classe il cui nome è Quadrato. Notare che in Ruby il nome della classe deve iniziare con una maiuscola.

Il quadrato ha quattro lati tutti della stessa lunghezza, mettiamo nella classe la lunghezza del lato, chiamiamola lung_lato, così:

class Quadrato
    attr_accessor :lung_lato
end

Qui attr_accessor è un accessorio per accedere a lung_lato. Possiamo adesso usare la nostra classe, creando un’istanza, così:

#!/usr/bin/ruby
# quadrato.rb

class Quadrato
    attr_accessor :lung_lato
end

q1 = Quadrato.new # crea un quadrato
q1.lung_lato = 5 # definisce la lunghezza di q1

puts "Il lato del quadrato q1 vale #{q1.lung_lato}"

quadrato

Allora: con Quadrato.new abbiamo definito un nuovo quadrato, chiamato q1, gli abbiamo definito la misura del lato utilizzando l’attributo lung_lato. Abbiamo poi chiesto la misura del lato, sempre con lo stesso attributo, sì può essere usato per settare che per accedere al valore in esso memorizzato (set e get nel gergo dei programmatori).

Nella classe quadrato adesso abbiamo una variabile (lung_lato) ma possiamo definire anche funzioni, per esempio area e perimetro, così:


class Quadrato
    attr_accessor :lung_lato

    def area
        @lung_lato * @lung_lato
    end

    def perimetro
        4 * @lung_lato
    end       
end

Le funzioni area e perimetro sono simili a quelle che abbiamo già visto nei post precedenti. Sono però contenute all’interno della classe, vediamo un esempio d’uso:

#!/usr/bin/ruby
# quad1.rb

class Quadrato
    attr_accessor :lung_lato

    def area
        @lung_lato * @lung_lato
    end

    def perimetro
        4 * @lung_lato
    end
end

q1 = Quadrato.new # crea un quadrato
q1.lung_lato = 5 # definisce la lunghezza di q1

puts "Lato = #{q1.lung_lato}"
puts "Area = #{q1.area}"
puts "Perimetro = #{q1.perimetro}"

quad1
OK, l’unica cosa nuova è l’uso dell’attributo accessorio lung_lato: per dire che vogliamo usare quello all’interno della classe dobbiamo prefissatlo con @. Questo lo identifica come una variabile della classe.

Inizializzatori e costruttori

Quando scriviamo q = Quadrato.new un nuovo quadrato viene creato. È possibile precisare all’atto della creazione alcune caratteristiche, anzi tutte quelle che vogliamo, ecco come:

#!/usr/bin/ruby
# quad2.rb

class Quadrato
    attr_accessor :lung_lato

    def initialize lung_lato = 0
        @lung_lato = lung_lato
    end

    def area
        @lung_lato * @lung_lato
    end

    def perimetro
        4 * @lung_lato
    end
end

q4 = Quadrato.new 4

q5 = Quadrato.new
q5.lung_lato = 5

puts "Lato = #{q4.lung_lato}"
puts "Area = #{q4.area}"
puts "Perimetro = #{q4.perimetro}"
puts ""
puts "Lato = #{q5.lung_lato}"
puts "Area = #{q5.area}"
puts "Perimetro = #{q5.perimetro}"

quad2

Notare la funzione initialize, righe 7-9, che ci permette di definire lung_lato durante la creazione. Questa funzione ha un valore di default, 0 nel nostro caso.

Possiamo così creare q4 passandogli subito la lunghezza del lato, riga 20.
In alternativa possiamo creare q5, riga 22 e definire dopo la lunghezza del lato, riga 23.

OK, per oggi basta così 😉 😀