Author: Massimo Maria Ghisalberti - pragmas.org (massimo.ghisalberti@pragmas.org)

Date: 2014-10-21

Emacs 25.3.50.1 (Org mode 9.1.2)

Validate

Programmazione elementare in Ruby.

1 Premessa al corso di programmazione elementare

Il mondo moderno è immerso nella tecnologia. Oggi sarebbe impensabile pensare ad una vita slegata dagli strumenti tecnologici che usiamo tutti i giorni, che siano semplici interruttori o automobili. I computer oggi sono ubiquitari, che ce ne accorgiamo o no; sono nelle nostre lavatrici, nelle automobili, negli aerei, nei telefoni fino dal più semplice e non smart.

La popolazione è sostanzialmente divisa in nativi e non nativi digitali, persone che hanno potuto accedere al computer in età adulta o post-adolescenziale e persone che sono nate e si sono formate in piena rivoluzione informatica. Gli approcci delle due fasce di popolazione sono sostanzialmente diversi: La prima generalmente possiede una certa diffidenza naturale verso il mezzo informatico, mentre la seconda lo usa con naturalezza e propensione.

Il computer è un superbo mezzo di comunicazione, di lavoro e svago; sempre più potente ed inserito nella vita normale. È un mezzo, per cui né buonocattivo, dipende solo dall'uso che ne viene fatto. Il nativo digitale a causa della sua naturalezza all'uso del mezzo informatico è oggi un soggetto potenzialmente a rischio. La maggior parte di noi è consapevole, per esempio, dell'approccio passivo alla televisione; della comunicazione a senso unico che porta e di come in taluni casi questa informazione possa essere manipolata e non corretta. Nessuno di noi però, è ancora propriamente consapevole dei pericoli insiti nell'uso acritico e non corretto dei nostri piccoli ed ubiquitari computer.

Abbiamo telefoni intelligenti, tablet o notebook che portiamo ovunque andiamo. I nostri figli li hanno, mini e microcomputer perennemente connessi ad Internet, dove possono fruire più o meno liberamente dei contenuti più disparati. Vietare non è la soluzione, impedirgli di usarli non è la soluzione ma solo un tampone temporaneo che presto imparerà a scavalcare. Si dovrebbe insegnare ai nostri figli, nativi digitali, l'approccio consapevole ed informato.

Gli approcci agli insegnamenti informatici vanno rivisti e va rivista l'età in cui iniziare. Le lezioni di avviamento al computer che venivano e vengono utilizzati oggi non sono più adatti per i nativi digitali. Accendere un computer e lanciare una applicazione per i nostri figli è naturale come per noi accedere il televisore e cambiare canale. Usare un gioco, che è un software, è naturale per loro. Passivamente naturale, il mito della interattività è solo un percorso preordinato tra tanti progettati a monte dell'utente e si è passivi nell'illusione di non esserlo. Bisogna passare dalla semplice visione strumentale e alfabetizzazione informatica alla fase di acquisizione delle competenze digitali.

Per questo motivo, si dovrebbe insegnare la programmazione dei computer ai bambini in età scolare. Non per creare una generazione di programmatori (come mi si è accusato), ovviamente, ma per rendere delle persone consapevoli ed attive di fronte al mezzo informatico.

Programmare un computer è affrontare e risolvere problemi, problem posing and solving, è imparare a ordinare i pensieri per poter realizzare le idee. L'approccio può essere multidisciplinare e il metodo applicabile in molti campi e non necessariamente informatici. Lo studio della programmazione è quindi, oltre alla sua mera applicazione, una formidabile palestra di logica che guida il ragazzo attraverso un percorso che passa per l'analisi, la scomposizione dei problemi, la verifica dei risultati e l'organizzazione del pensiero.

Imparare a programmare, insegna la collaborazione e la condivisione (se insegnata nella maniera adeguata) tramite un processo di trial and error e permette allo studente di esprimersi in molti modi. L'approccio produttivo ed anche ludico, potrebbe permettere di attirare e coinvolgere studenti problematici e creare interesse.

Quando i bambini creano un progetto come questo, imparano anche a programmare, ma, cosa ancora più importante, programmano per imparare. Perché imparando a programmare, imparano mille altre cose, aprendo nuove opportunità di apprendimento. Ripeto, è utile fare un'analogia con la lettura e la scrittura. Quando si impara a leggere e scrivere, si aprono nuove opportunità per imparare molte altre cose. Quando si impara a leggere, allora si potrà anche leggere per imparare. E imparare a programmare è la stessa cosa. Se si impara a programmare, allora si potrà anche programmare per imparare. Ora, alcune cose che si imparano sono piuttosto ovvie. Si impara qualcosa di più su come funzionano i computer. Ma questo è solamente il punto di partenza. Quando si impara a programmare, si apre la possibilità di imparare molte altre cose.

As kids are creating projects like this, they're learning to code, but even more importantly, they're coding to learn. Because as they learn to code, it enables them to learn many other things, opens up many new opportunities for learning. Again, it's useful to make an analogy to reading and writing. When you learn to read and write, it opens up opportunities for you to learn so many other things. When you learn to read, you can then read to learn. And it's the same thing with coding. If you learn to code, you can code to learn. Now some of the things you can learn are sort of obvious. You learn more about how computers work. But that's just where it starts. When you learn to code, it opens up for you to learn many other things.

Mitch Resnick (http://www.ted.com/talks/mitch_resnick_let_s_teach_kids_to_code/transcript)

È nel contempo importante, insegnare come l'approccio al software ed alla sua produzione debba essere morale spingendo lo studente verso il concetto di Open Source, spostando il valore dal prodotto in sé al produttore (il programmatore); combattendo la visione monopolistica delle grandi software house o software service verso un approccio sostanziale alle tipologie software (anche solo nel rispetto delle direttive ministeriali in materia di software da utilizzare nelle pubbliche amministrazioni).

Non va dimenticato inoltre, all'atto pratico, come in futuro la richiesta di programmatori diventerà altissima e quindi si potrebbe fornire agli studenti uno strumento in più. Esistono numerose esperienze, specialmente negli U.S.A., di insegnamento della programmazione a bambini in età scolare (k-12); mentre per l'Europa si potrebbero citare le esperienze fatte in UK ed Estonia.

1.1 Ambienti liberi per l'insegnamento della programmazione a bambini e ragazzi.

Per avvicinare gli studenti alla programmazione informatica esistono già numerosi strumenti con un approccio più o meno visuale. In questa sede si prenderanno in considerazione solo strumenti Open Source per i motivi espressi sopra.

1.1.1 Scratch

È una piattaforma sviluppata al MIT (Massachusetts Institute of Technology) nel Lifelong Kindergarten Group (http://scratch.mit.edu/) e progettata specificatamente per bambini di età scolare e prescolare. L'approccio è totalmente visuale con blocchi logici colorati e di facile comprensione. È adatto per insegnare la strutturazione del flusso delle istruzioni rendendola facilmente identificabile con risultati rapidi e non frustranti per il bambino. Ne esistono attualmente due versioni una scritta in Smalltalk (http://scratch.mit.edu/scratch_1.4), quella originale, mentre una seconda fruibile online e scritta in ActionScript (http://scratch.mit.edu/projects/editor/?tip_bar=getStarted) o scaricabile (http://scratch.mit.edu/scratch2download/).

Scratch ha dato vita a numerosi progetti simili, come per esempio AppInventor (http://appinventor.mit.edu/explore/), Blockly (https://code.google.com/p/blockly/), Stencyl (http://www.stencyl.com/).

1.1.2 Alice

Alice (http://www.alice.org/index.php) è un ambiente visuale per la creazione di mondi virtuali in 3D ed è il risultato di un progetto della Carnegie Mellon University di Pittsburgh (Pennsylvania).

1.1.3 Hackety Hack

È un piccolo ambiente (http://hackety.com/) per l'insegnamento alla programmazione nel linguaggio Ruby (https://www.ruby-lang.org/it/). Il linguaggio di programmazione Ruby è nato nel 1992 scritto da Yukihiro Matsumoto. Oggi è un linguaggio molto popolare (anche se non popolarissimo in Italia). Le sue peculiari caratteristiche di immediatezza nell'utilizzo ne fanno un buon candidato per l'insegnamento anche a bambini in età scolare.

1.1.4 KidsRuby

KidsRuby (http://www.kidsruby.com/) è una evoluzione di Hackety Hack. Come il suo ispiratore usa Ruby come linguaggio.

1.1.5 Kojo

Kojo è sviluppato da Lalit Pant (Himjyoti school, Dehradun - India) ed è utilizzato in varie scuole indiane, statunitensi, inglesi e svedesi. L'approccio usato nella piattaforma Kojo (http://www.kogics.net/kojo) è più ampio dei precedenti. Può essere rivolto a più livelli di apprendimento ed è dotato di parti specifiche, per esempio per la sperimentazione in ambito matematico con un laboratorio basato su GeoGebra (http://www.geogebra.org/cms/it/). Il linguaggio utilizzato è Scala (http://www.scala-lang.org/). Scala è un linguaggio estremamente potente e multiparadigma (Orientato agli oggetti, funzionale) che può essere utilizzato a vari livelli, sufficientemente semplice da poter essere insegnato in età scolare (dalla classe 4° primaria). La sua caratteristica di linguaggio funzionale lo fa particolarmente utile nella risoluzione di problemi matematici. I linguaggi funzionali sono una modalità di programmazione in forte ascesa in questi ultimi anni e sicuramente lo saranno anche per i prossimi.

2 Perché Ruby direttamente e non un ambiente facilitato.

È una scelta difficile non partire con un ambiente facilitato, come Scratch per esempio. Scratch è sicuramente un ottimo ambiente per l'insegnamento della logica di programmazione ai bambini. Scratch è colorato e simpatico, ha una mascotte (un gatto) e permette velocemente di ottenere risultati con grafica e suoni, sembra un gioco. Il suo sembrare troppo un gioco tende però a fuorviare i bambini dallo scopo primario. I nostri ragazzi non hanno una mentalità anglosassone e sono meno organizzati e disciplinati degli anglosassoni, il che rende ambienti troppo ludici dispersivi. I bambini nativi digitali tenderanno a giocare troppo con gli strumenti; potendo, per esempio, facilmente disegnare sprite (oggetti grafici) e farli muovere manualmente.

Scopo di un corso di programmazione è anche aumentare la percezione del computer stesso, come un insieme di hardware e sistema operativo con software applicativo. L'utente dovrebbe distinguere ed utilizzare le varie parti e capirne le relazioni, altrimenti si potrebbe incorrere nello stesso errore: il nascondere parti sostanziali; che renderà l'utilizzo dello strumento informatico più o meno passivo.

Inoltre, questi ambienti sono troppo orientati ad una logica di tipo imperativo e legati ad un diagramma di flusso di, ormai, vecchia concezione. Un problema tipico dei programmatori di computer è il cambio di paradigma come molti studi ed esperienze dimostrano. Cambiare stile di programmazione è difficile e quindi introdurre i giovani verso linguaggi multiparadigma potrà solo renderli più flessibili in futuro.

3 Programmare in Ruby

3.1 Premessa

Questo sarà un piccolo corso per l'avvio alla programmazione in Ruby, un linguaggio estremamente semplice e divertente ma non per questo banale. Ruby ha in sé tutte le caratteristiche per essere un ottimo linguaggio di programmazione multiparadigma ed essere adatto per l'insegnamento delle basi della moderna programmazione dei computer.

Ruby è divertente.

3.2 cosa è Ruby

Questo è copiato semplicemente dal sito web di Ruby:

Ruby è un linguaggio di equilibrio e armonia. Il suo creatore, Yukihiro “Matz” Matsumoto, ha fuso insieme parti dei suoi linguaggi di programmazione preferiti (Perl, Smalltalk, Eiffel, Ada e Lisp) per creare un nuovo linguaggio in grado di bilanciare programmazione funzionale con programmazione imperativa.

Lui stesso ha detto più volte che sta continuamente "provando a rendere Ruby naturale, non semplice", in un modo che rispecchia la vita.

Chiarificando questo concetto, Matz aggiunge:

Ruby è apparentemente semplice, ma al suo interno è molto complesso, proprio come il corpo umano.

3.3 Cosa serve

Ruby è un linguaggio di programmazione. Un linguaggio di programmazione è, come un linguaggio naturale, un insieme di regole e convenzioni per permetterci di parlare con i computer. I computer sono macchine di calcolo stupide e devono essere istruite. Per utilizzare un linguaggio di programmazione serve un software, un programma per computer. È il problema del cane che si morde la coda, per scrivere un software ci vuole un altro software. Quindi per usare Ruby dobbiamo installare l'interprete Ruby. Qui potremmo entrare in una distinzione inizialmente un po' difficile: cosa è un interprete e cosa è un compilatore. La differenza oggi è molto labile, ma comunque in generale:

  • l'interprete è un software che legge, interpreta direttamente ed esegue un programma scritto in un linguaggio.
  • un compilatore è un software che legge, interpreta e trasforma in dati binari un programma scritto in un linguaggio, salvando sul disco un file detto eseguibile che potrà essere lanciato (eseguito) direttamente.

Quindi abbiamo bisogno dell'interprete Ruby.

Questo software è multipiattaforma, il che vuol dire che può funzionare (più o meno bene) su molti sistemi operativi.

Cosa è un sistema operativo:

  • Un sistema operativo è il software di base del computer, senza esso non si potrebbe utilizzare.

Di sistemi operativi ne esistono molti. Forse il più conosciuto è Microsoft Windows(tm) ma non è il solo. Esistono le distribuzioni Linux, per esempio, il MacOSX(tm) o l'Android che avete probabilmente nei tablet o telefoni. Ne esistono davvero molti, a pagamento o gratuiti e per molteplici utilizzi.

Ruby funziona in molti di essi.

Ruby ha un sito web dedicato: https://www.ruby-lang.org, dove ci sono tantissime informazioni su di esso, dalla sua storia alla documentazione e per finire al software stesso e le indicazioni su come installarlo (cioè metterlo) nel computer che possedete. L'interprete può essere prelevato come codice sorgente, ha una licenza Open Source, compilarlo ed installarlo; altrimenti se ne può utilizzare una versione già pronta per il sistema operativo che volete. Come si può vedere dalle pagine web relative: https://www.ruby-lang.org/it/downloads/ e https://www.ruby-lang.org/it/installation/, ci sono varie implementazioni dell'interprete.

Installate o fatevi installare la più adatta.

Vi dovete anche procurare un editor di testo per programmatori. È un più o meno semplice programma per scrivere, diverso dal word processor che usate per scrivere. Spesso questi editor hanno la possibilità di evidenziare la sintassi (le parole) del linguaggio in colori diversi, permettendo di poter trovare le parti del programma che state scrivendo a colpo d'occhio e limitandone anche gli errori di battitura.

Qui: https://www.ruby-lang.org/it/documentation/, ne trovate una lista in fondo alla pagina, sappiate comunque che esistono tantissimi editor per programmatori che lì non sono elencati. Alcuni sono a pagamento ed altri sono gratuiti, open Source o no.

Ruby è un linguaggio diffuso oggi, quindi un qualunque editor per programmatori avrà una evidenziazione colorata per la sua sintassi.

3.4 Le basi

In questo capitolo vedremo la base della programmazione in Ruby, alcuni elementi chiave e tipi di dati più diffusi.

3.4.1 Iniziamo con i numeri e le stringhe

Ruby ha anche un REPL, Read Eval Print Loop, che a parte la parola difficile sta a significare che si possono mandare direttamente comandi all'interprete. Nella distribuzione di Ruby c'è un programma (scritto in Ruby) che si chiama irb (interactive ruby). Per lanciarlo dovete saper usare un minimo un terminale dei comandi. Un terminale dei comandi è, nel vostro sistema operativo moderno, una finestra in cui lo sfondo è spesso nero con un cursore che lampeggia. Ci potete scrivere dentro e mandare comandi al sistema operativo.

Cercate nei vai menù del sistema qualcosa del tipo: prompt dei comandi o terminale

Lanciata la console dei comandi ci scrivete dentro irb e premete invio.

irb(main):001:0> 

A questo punto potete inviare comandi all'interprete e confermare premendo invio.

irb(main):001:0>  1 + 2
=> 3
irb(main):002:0>

Se scrivete 1 + 2 avrete il risultato. Ruby è anche una calcolatrice. Provate a fare dei calcoli con le operazioni che conoscete, saranno rispettate le regole matematiche di precedenza degli operatori che vi sono state insegnate:

  • + addizione
  • - sottrazione
  • * moltiplicazione
  • / divisione
  • % modulo
  • ** esponente

Bisogna stare attenti alla divisione, come la maggior parte dei linguaggi di programmazione. Se per esempio:

irb(main):013:0> 10 / 7
=> 1
irb(main):014:0> 

potete vedere come il risultato è 1. Per trovare il resto della divisione dovete effettuare una operazione di modulo che vi restituisce il resto:

irb(main):014:0> 10 % 7
=> 3
irb(main):015:0> 

che ovviamente è 3. Quindi l'operatore / nel caso in cui i due numeri (operandi) siano di tipo intero eseguirà una divisione intera, troncando la parte decimale.

Per avere la parte decimale si deve fare così:

irb(main):015:0> 10 / 7.0
=> 1.4285714285714286
irb(main):016:0> 

e cioè scrivere almeno uno degli operandi nella forma cosiddetta a virgola mobile o flottante (float). L'interprete allora saprà che dovrà eseguire una divisione non intera. Nel caso in cui facciate una operazione di modulo con almeno un operando di tipo float

irb(main):016:0> 10 % 7.0
=> 3.0
irb(main):017:0> 

il risultato sarà come per la divisione intera ma con il tipo del risultato in virgola (float). Ruby supporta le parentesi tonde che possono essere annidate (scritte le une dentro le altre) liberamente. Come per gli altri linguaggi di programmazione le parentesi graffe e quadre sono usate per altri scopi e non quello di alterare le precedenze degli operatori o raggruppare i calcoli.

irb(main):017:0> (2 + 3) * 2
=> 10
irb(main):018:0>

I linguaggi di programmazione sono in grado di manipolare numerosi tipi di dati e fino a qui ne abbiamo visti due:

  • numeri interi (integer)
  • numeri in virgola mobile (float)

questi sono chiamati in Ruby Fixnum e Float rispettivamente.

Per esempio:

irb(main):019:0> 1.class
=> Fixnum
irb(main):020:0> 1.0.class
=> Float
irb(main):021:0> 

Vedremo in seguito cosa significa 1.class e 1.0.class, per adesso lasciamola come una cosa magica di Ruby: la possibilità di sapere quando serve il tipo di un certo dato (la riflessione).

Un tipo di dato importantissimo in cui Ruby eccelle sono le stringhe (String). Le stringhe sono una sequenza di caratteri, un testo insomma, una dietro l'altra compresi gli spazi ed i vari segni di punteggiatura.

irb(main):021:0> "ciao mondo"
=> "ciao mondo"
irb(main):022:0> 'ciao modo'
=> "ciao modo"
irb(main):023:0> 

Come potete vedere si possono usare sia le virgolette singole che quelle doppie. In questo caso non c'è differenza ma in altri sì e vedremo in seguito. Le stringhe come i numeri possono essere sommate (concatenate):

irb(main):024:0> "ciao" + "mondo"
=> "ciaomondo"
irb(main):025:0> 

ma non sottratte (almeno non così):

irb(main):025:0> "ciao" - "mondo"
NoMethodError: undefined method `-' for "ciao":String
  from (irb):25
  from /home/nissl/bin/ruby-2.1/bin/irb:11:in `<main>'
irb(main):026:0> 

Come vedete l'interprete ha dato un errore e si è fermato comunicandolo. In questa maniera possiamo sapere dove è l'errore e che tipo di errore è.

Le stringhe possono però essere moltiplicate per un numero ma non per un'altra stringa:

irb(main):028:0> 'pippo' * 5
=> "pippopippopippopippopippo"
irb(main):029:0> 

irb(main):029:0> 'pippo' * 'pippo'
TypeError: no implicit conversion of String into Integer
  from (irb):29:in `*'
  from (irb):29
  from /home/nissl/bin/ruby-2.1/bin/irb:11:in `<main>'
irb(main):030:0> 

In questo esempio ho moltiplicato una stringa per un numero e sommata ad un'altra:

irb(main):031:0> "pippo" * 5 + "pluto"
=> "pippopippopippopippopippopluto"
irb(main):032:0> 

Come potete vedere, le regole di precedenza sono state applicate: prima la moltiplicazione e poi la somma. Nelle stringhe è possibile fare degli errori strani.

Guardate questa:

'pippo è andato dall'altra parte'

Questo è un errore. Come potete vedere la stringa inizia con una virgoletta singola (apice singolo) e termina con una virgoletta singola. Dove termina però? La colorazione della sintassi può aiutare a capire. Per l'interprete la stringa termina dopo la parola dall. Il resto non è nella stringa. Per superare problemi come questo ci sono due modi principali:

'pippo è andato dall\'altra parte'
"pippo è andato dall'altra parte"

Ho usato nella prima riga il carattere \ (escape) che dice a Ruby di trattare il carattere che subito lo segue come un carattere della stringa e non come un terminatore della stessa. Nel secondo caso, ho racchiuso la stringa tra virgolette doppie (apice doppio) e quindi ho potuto usare l'apice singolo liberamente.

Ora che ci siamo va capita bene la differenza tra numeri e stringhe:

  • 25 è un numero
  • '25' è una stringa
  • "25" è una stringa
  • '25 * 25' è una stringa e non una operazione
  • 25 * 25 è una moltiplicazione tra due numeri
  • "pippo" * 5 va bene e verrà ripetuta la parola pippo per cinque volte
  • 5 * "pippo" non va bene perché significa pippo volte il numero 5.

Provate tutte le possibili varianti che vi vengono in mente.

3.4.2 Le variabili e gli assegnamenti

Ogni linguaggio d programmazione che si rispetti, ci permette di calcolare cose che non sappiamo a priori, altrimenti non servirebbero a niente. Fino ad adesso avete visto come utilizzare dei valori, ora vediamo come creare valori e recuperarli per quando ci servono.

Le variabili sono come delle scatole dove mettete delle cose e l'assegnamento è l'azione del mettercele dentro. Direi che è semplice.

numero_delle_pere = 12
numero_delle_pere_vendute = 5

Ho messo nella variabile numero_delle_pere la quantità di pere del contadino prima di andare al mercato. Al mercato il contadino fa il conto e mette nella variabile numero_delle_pere_vendute le pere che ha venduto e poi:

numero_delle_pere_rimaste = numero_delle_pere - numero_delle_pere_vendute

Il contadino ora sa che la variabile numero_delle_pere_rimaste contiene il numero 7.

irb(main):045:0> numero_delle_pere = 12
=> 12
irb(main):046:0> numero_delle_pere_vendute = 5
=> 5
irb(main):047:0> numero_delle_per_rimaste = numero_delle_pere - numero_delle_pere_vendute
=> 7
irb(main):048:0> 

Possiamo mettere dentro la nostra scatola quello che vogliamo. La comodità è che così noi ricordiamo più facilmente il nome piuttosto che il numero contenuto. È come quando vogliamo telefonare alla mamma, basta col nostro telefono scrivere mamma o dire mamma, il telefono saprà il numero.

Possiamo fare con le variabili quello che facciamo con i valori: moltiplicarle, dividerle, sommarle o sottrarle… e tante altre cose che vedremo.

3.4.3 Scriviamo un programma

Fino ad adesso abbiamo usato per fare gli esperimenti il REPL. Adesso scriveremo un programma.
Lanciate l'editor di testo e create un nuovo file. Sarete davanti ad un programma che vi permetterà di scrivere.

Abbiamo un problema:

Ci sono dieci bambini che devono portare delle pere ad una festa, ognuno ha 3 pere. Tre di questi bambini ne mangiano due ciascuno. Quante pere arriveranno alla festa.

Scrivete nel vostro editor di testo.

Questi sono i dati:

bambini = 10
pere_per_bambino = 3
bambini_che_hanno_mangiato_le_pere = 3
pere_mangiate_per_bambino = 2

Ora un po' di calcoli:

pere_totali = bambini * pere_per_bambino
pere_mangiate = bambini_che_hanno_mangiato_le_pere * pere_mangiate_per_bambino
pere_arrivate_alla_festa = pere_totali - pere_mangiate

Aggiungete poi questo:

puts "Alla festa sono arrivate:"
puts pere_arrivate_alla_festa
puts "pere"

Ora potete salvare il file come pere.rb

Il vostro programma completo sarà questo:

bambini = 10
pere_per_bambino = 3
bambini_che_hanno_mangiato_le_pere = 3
pere_mangiate_per_bambino = 2

pere_totali = bambini * pere_per_bambino
pere_mangiate = bambini_che_hanno_mangiato_le_pere * pere_mangiate_per_bambino
pere_arrivate_alla_festa = pere_totali - pere_mangiate

puts "Alla festa sono arrivate:"
puts pere_arrivate_alla_festa
puts "pere"

Avete scritto un programma per risolvere il problema delle pere. Ora dovete farlo funzionare. Ritorniamo al terminale di prima e nella cartella dove avete salvato il file scrivete:

ruby pere.rb

Il file che contiene il programma sarà caricato da ruby ed eseguito. Se tutto è andato bene avrete avuto questo:

➜  source  ruby pere.rb 
Alla festa sono arrivate:
24
pere
➜  source  

Complimenti!

Certo non è proprio bello, il risultato lo abbiamo su tre righe, non sarebbe meglio avere: Alla festa sono arrivate 24 pere? Direi di si.
Allora facciamo in questo modo:

bambini = 10
pere_per_bambino = 3
bambini_che_hanno_mangiato_le_pere = 3
pere_mangiate_per_bambino = 2

pere_totali = bambini * pere_per_bambino
pere_mangiate = bambini_che_hanno_mangiato_le_pere * pere_mangiate_per_bambino
pere_arrivate_alla_festa = pere_totali - pere_mangiate

puts "Alla festa sono arrivate:" + pere_arrivate_alla_festa + "pere"

Lo salviamo come pere2.rb e come prima scriviamo nel terminale:

ruby pere2.rb

Però c'è un problema:

➜  source  ruby pere2.rb
pere2.rb:11:in `+': no implicit conversion of Fixnum into String (TypeError)
  from pere2.rb:11:in `<main>'
➜  source  

Ruby non è riuscito a utilizzare la variabile pere_arrivate_alla_festa come se fosse una stringa e quindi ci avverte. Per non incorrere in questo errore dobbiamo convertire il numero contenuto nella variabile in una stringa:

puts "Alla festa sono arrivate:" + pere_arrivate_alla_festa.to_s + "pere"

A pere_arrivate_alla_festa aggiungiamo .to_s e magicamente diventa una stringa.

Combiamo il programma così e salviamo come pere3.rb

bambini = 10
pere_per_bambino = 3
bambini_che_hanno_mangiato_le_pere = 3
pere_mangiate_per_bambino = 2

pere_totali = bambini * pere_per_bambino
pere_mangiate = bambini_che_hanno_mangiato_le_pere * pere_mangiate_per_bambino
pere_arrivate_alla_festa = pere_totali - pere_mangiate

puts "Alla festa sono arrivate: " + pere_arrivate_alla_festa.to_s + " pere"

Eseguite il programma come prima:

➜  source  ruby pere3.rb
Alla festa sono arrivate: 24 pere
➜  source  

Meglio no?

3.4.4 Riflessioni fino qui

In questa ultima parte abbiamo visto delle cose strane, delle istruzioni che sicuramente non abbiamo capito. Per primo vediamo l'istruzione puts. È un metodo. Nei linguaggi di programmazione si sente spesso parlare di funzioni, procedure o metodi; sono tutti un modo per indicare dei piccoli pezzi codice (il linguaggio di programmazione scritto) che possono essere riutilizzati tramite un nome che gli abbiamo dato. In Ruby, come in altri linguaggi simili, le funzioni vengono chiamate metodi. Il metodo puts serve per scrivere del testo in uscita. Spieghiamo questa cosa complicata con un esempio.

Immaginate una stanza con due porte, da una si entra e da una si esce. Quella da cui si entra, si chiama: input; quella da cui si esce: output. Mettiamo che una sera la mamma debba uscire col babbo per una cena. La mamma si vuole truccare per farsi bella e quindi:

  • entra dalla porta input
  • dentro la stanza viene truccata da una estetista e diventa più bella
  • esce dalla porta output

Insomma, la mamma sono i dati del metodo (la stanza dell'estetista) che entra struccata ed esce truccata.

I dati (numeri o stringhe) entrano come parametri nel metodo e ne escono modificati o no.

Il metodo puts è un metodo che Ruby fornisce da solo, come molti altri, ma noi li possiamo anche scrivere e cioè definire quando vogliamo. La definizione di puts ed il suo uso, per esempio, potrebbe essere come questa:

def puts(testo_in_output)
  testo_in_output
end

#si può invocare come:
puts "ciao mondo"

#oppure:

puts("ciao mondo")

Come vedete possiamo invocare (cioè chiamare o usare) il metodo in due modi. Ruby permettere di non scrivere (omettere) le parentesi in alcuni casi. Le righe che cominciano con # sono considerate dei commenti, del testo che possiamo scrivere dentro il codice senza che esso venga letto dall'interprete. Il commento va dal carattere # fino alla fine della riga. Se andate a capo senza mettere come primo carattere # Ruby vi darà un errore. Si possono fare anche commenti su più righe.

Per ricapitolare:

#questo è un commento che arriva fino in fondo alla riga

#questo commento è
sbagliato

=begin
questo
commento
va
bene
=end

La possibilità di commentare un codice è molto importante, perché così possiamo scrivere del testo per ricordarci cosa stiamo facendo o per descriverlo ad altri che lo leggeranno in seguito.

Vediamo ora l'altro metodo che abbiamo usato: to_s
Lo abbiamo usato in questo modo:

pere_arrivate_alla_festa.to_s

per convertire un numero intero in una stringa. Il metodo to_s significa semplicemente: to string (in stringa). Ricordate che la lingua inglese è la lingua ufficiale per i linguaggi di programmazione, quindi le cose si chiameranno sempre in inglese.
Il metodo to_s non ha dati in entrata (input), ma ugualmente fa qualcosa. Come vedete viene applicato invece a certi dati tramite il punto: 10.to_s fa diventare il 10 una stringa, "10".
Dovete sapere che Ruby è un linguaggio /orientato agli oggetti (Object Oriented)
e cioè un linguaggio in cui noi possiamo descrivere o definire degli oggetti che comunicano con altri oggetti.

Complicato?

Vediamo un po'.

Molti di voi giocheranno coi mattoncini Lego(tm) o almeno saprete sicuramente cosa sono. Sapete che ci sono molti mattoncini di forme diverse che si attaccano insieme. Bene! I mattoncini Lego(tm) sono come un linguaggio Object Oriented. I mattoncini sono oggetti. Se guardate un mattoncino, noterete che è diverso sopra e sotto, per permettervi di attaccarli insieme. La parte dove si attaccano è una interfaccia di collegamento. I rilievi rotondi su una delle superfici sono i metodi. In Ruby si dice che tutto è un oggetto (è un linguaggio ad oggetti puro), quindi anche le stringhe ed i numeri.

  • L'oggetto delle stringhe si chiama: String.
  • L'oggetto dei numeri si chiama: Fixnum.

Il metodo to_s è quindi un metodo dell'oggetto Fixnum. È un metodo però che tutti gli oggetti in Ruby ha, come molti altri; ma li vedremo in seguito.

Il modo di invocare (chiamare o call) è quello tramite la dot notation, la notazione a punto. Insomma per farla semplice: mettete un punto tra l'oggetto e il metodo.
Ricordate come lanciare irb?

irb(main):001:0> 10.to_s
=> "10"
irb(main):002:0> io_sono_un_numero = 12
=> 12
irb(main):003:0> io_sono_un_numero.to_s
=> "12"
irb(main):004:0> 

Questi per esempio sono i metodi di Fixnum:

irb(main):004:0> Fixnum.methods
=> [:allocate, :superclass, :freeze, :===, :==, :<=>, :<, :<=, :>, :>=, :to_s, :inspect, :included_modules, :include?, :name, :ancestors, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :const_missing, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant, :private_constant, :singleton_class?, :include, :prepend, :module_exec, :class_exec, :module_eval, :class_eval, :method_defined?, :public_method_defined?, :private_method_defined?, :protected_method_defined?, :public_class_method, :private_class_method, :autoload, :autoload?, :instance_method, :public_instance_method, :nil?, :=~, :!~, :eql?, :hash, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
irb(main):005:0> 

Come vedete sono parecchi ed alcuni anche molto strani… Cercate to_s.

Quello che ho chiamato per sapere questa lista è methods. Il metodo methods fa questo: restituisce una lista dei nomi dei metodi dell'oggetto su cui è invocato.

Nel gergo dei linguaggi orientati agli oggetti (puri) si dice: mandare un messaggio ad un oggetto. Quindi in pratica ho chiesto a Fixnum di dirmi tutti i nomi dei suoi metodi. Come quando chiedete ad un amico di darvi il suo quaderno di matematica e lui ve lo da.

3.4.5 Confrontare le cose e decidere cosa fare

Cominciamo a vedere le cose serie. Un programma non è un programma se non prendiamo decisioni. Fino ad adesso abbiamo solo dato comandi ed è il momento di decidersi a decidere.

Per prima cosa guardiamo gli operatori booleani (si chiamano booleani da George Bool un matematico inglese dell'800):

  • > maggiore di…
  • < minore di…
  • >= maggiore od uguale di…
  • <= minore od uguale di…
  • == uguale a…
  • != non uguale a…
irb(main):001:0> 5 > 2
=> true
irb(main):002:0> 5 < 2
=> false
irb(main):003:0> 5 >= 5
=> true
irb(main):004:0> 5 <=5
=> true
irb(main):005:0> 5 == 5
=> true
irb(main):006:0> 5 != 5
=> false
irb(main):007:0> 

Con questi operatori possiamo sapere quindi se un numero è maggiore, minore od uguale ad un altro.

Lo possiamo fare anche con le stringhe:

irb(main):001:0> 5 > 2
=> true
irb(main):002:0> 5 < 2
=> false
irb(main):003:0> 5 >= 5
=> true
irb(main):004:0> 5 <=5
=> true
irb(main):005:0> 5 == 5
=> true
irb(main):006:0> 5 != 5
=> false
irb(main):007:0> 

Questo non vi fa domandare cosa succede con le stringe? È interessante perché il confronto è fatto sul valore lessicale delle stringhe.

Guardate:

irb(main):013:0> 'a' > 'b'
=> false
irb(main):014:0> 'b' > 'a'
=> true
irb(main):015:0> 

Capito? L'ordine lessicale è praticamente quello come nel dizionario con però un problema:

irb(main):014:0> 'b' > 'a'
=> true
irb(main):015:0> 'B' > 'a'
=> false
irb(main):016:0> 

Come sarebbe? b è maggiore di a ma B no?

I computer ordinano in maniera diversa da noi, sono ancora più stupidi di quello che avevamo fino ad ora pensato, loro ordinano prima le lettere maiuscole e poi le minuscole (per la verità prima ordinano i numeri, poi le maiuscole e dopo le minuscole). Questo è un problema.

Dobbiamo quindi stare attenti, ma fortunatamente Ruby ci aiuta con il metodo downcase che mette la stringa tutta in minuscolo o upcase che la rende tutta maiuscola.

irb(main):018:0> 'B'.downcase
=> "b"
irb(main):019:0> 'B'.downcase > 'a'.downcase
=> true
irb(main):020:0> 'B' > 'a'
=> false
irb(main):021:0> 'a'.upcase
=> "A"
irb(main):022:0> 

Ora abbiamo capito gli operatori booleani e che ci facciamo? Li usiamo per decidere cosa fare.

Prendete l'editor che scriviamo un programma nuovo.

puts "Quanti anni hai?"

eta = gets

if eta == 9
  puts "fai la quarta elementare"
else
  puts "non fai la quarta elementare"
end

Salvate come avete imparato con il nome test.rb e poi eseguitelo:

➜  source  ruby test.rb 
Quanti anni hai?
8
non fai la quarta elementare

Ora però proviamo scrivendo nove quando lo chiede:

➜  source  ruby test.rb 
Quanti anni hai?
9
non fai la quarta elementare

Qualcosa non va.

Intanto capiamo cosa è gets. Ricordate puts e gli input e output… La mamma che va a cena? Bene. Il metodo gets carica i dati dall'input', cioè ci chiede di scrivere qualcosa e premere il tasto invio. I dati che prende li può caricare dentro una variabile. Noi abbiamo messo l'età dentro la variabile /eta (non ha l'accento perché come vi ho già detto i linguaggi di programmazione sono in inglese e l'inglese non ha accenti di questo tipo, quindi non tutti i caratteri possiamo usare per i nomi delle variabili o dei metodi. Non si possono usare nemmeno i caratteri degli operatori e se ci fate caso neanche gli spazi… ), poi con un test abbiamo controllato il valore:

if eta == 9
  puts "fai la quarta elementare"
else
  puts "non fai la quarta elementare"
end

cioè: se (if) eta è uguale a 9 scrivi fai la quarta elementare altrimenti (else) scrivi non fai la quarta elementare. Noi però abbiamo scritto 9, perché non ha funzionato? Ora ve lo spiego: il metodo gets legge i caratteri che scriviamo ma non sa che quelli sono numeri; per lui sono stringhe.

Ruby è un linguaggio dinamico e permette di valutare e confrontare le pere con le mele. La maestra vi avrà sicuramente detto che non si confrontano le mele con le pere… La maestra ha ragione, ma i linguaggi di programmazione spesso non vanno proprio a braccetto con la matematica.

Scrivere così è perfettamente lecito:

irb(main):023:0> "pere" == 10
=> false
irb(main):024:0>

e giustamente restituisce il valore falso (false). Come mai?

Perché una stringa non è un numero.

Ruby, se i tipi del confronto non sono uguali o riconducibili, non confronta i valori ma i tipi stessi. Sono sicuro che vi cominci ad arrivare un po' di mal di testa, quindi vediamo.

La stringa pere è di tipo String, il numero 10 è di tipo Fixnum (tutto questo lo abbiamo visto prima), Ruby lo sa che i tipi sono diversi e non può confrontare i valori, quindi ci dice che un tipo String non è un tipo Fixnum.

Lo so che la storia dei tipi è un po' complicata, ma piano piano la capirete e vedrete che è ganzissima.

Ritornando al problema di prima, quello del 9. Per far funzionare il programma noi dobbiamo convertire eta in un numero con il metodo to_i (to integer):

puts "Quanti anni hai?"

eta = gets.to_i

if eta == 9
  puts "fai la quarta elementare"
else
  puts "non fai la quarta elementare"
end

Però sarebbe meglio anche aggiungere un altro metodo: chomp; che rimuove il carattere di invio. Quando scrivete 9 e date invio, anche il carattere di invio viene mandato al programma. Ecco, chomp rimuove il carattere di invio dalla stringa (il carattere invisibile newline ovvero nuova riga).

puts "Quanti anni hai?"

eta = gets.chomp.to_i

if eta == 9
  puts "fai la quarta elementare"
else
  puts "non fai la quarta elementare"
end

Qui vedete anche un'altra cosa interessante, i metodi possono essere chained (concatenati); messi uno dopo l'altro e concatenati con il punto. Ogni metodo passerà a quello dopo il valore che ha elaborato:

gets legge '9' + newline, lo passa a chomp che leva il newline e passa '9' a to_i che lo trasforma in un intero dal valore 9

La condizione if valuta sempre una espressione che restituisce un valore booleano: true o false, vero o falso; quindi possiamo utilizzare una qualunque espressione che restituisce vero a falso. Ricordatelo, servirà. La condizione if può essere semplice:

if a > 1
  puts 'a è maggiore di uno'
end

o come abbiamo visto prima una alternativa contrassegnata dalla parola chiave (keyword) else (altrimenti). La cosa interessante è che dentro un blocco if..end o if..else..end possiamo mettere dentro, annidare, altre condizioni:

if a > 1
  puts 'a è maggiore di 1'
else
  if a == 0
    puts 'a è uguale zero'
  else
    puts 'a è minore di zero'
  end
end

Si potrebbe anche scrivere meglio, ma va bene per capire. Come vedete il test prima controlla se a è maggiore di 1, se non lo è ci chiediamo: a è uguale a zero? se lo è lo scriviamo, altrimenti sarà per forza minore di zero. In questa maniera, annidando delle condizioni abbiamo valutato i tre possibili stati di un numero:

  • è maggiore di zero
  • è zero
  • è minore di zero

Se modifichiamo il programma di prima e lo salviamo come test2.rb:

puts "Quanti anni hai?"

eta = gets.to_i

if eta == 9
  puts "fai la quarta elementare"
else
  if eta < 9
    puts "sei ancora piccolo e fai la prima o la seconda o la terza"
  else
    puts "Sei già laureato?"
  end
end

Pensate a quanti giochini potremmo fare con la possibilità di annidare le condizioni. Questo in gergo si chiama: ramo decisionale. Visto che parliamo di ramo decisionale prima di guardare i cicli (loop) vediamo un altro modo per prendere le decisioni. È un modo molto potente ed utile, che ci permette di scrivere meglio il programma di prima.

Salvatelo come test3.rb ed eseguite come avete imparato:

puts "Quanti anni hai?"

eta = gets.to_i

case eta
when 6
  puts 'fai la prima elementare'
when 7
  puts 'fai la seconda elementare'
when 8
  puts 'fai la terza elementare'
when 9
  puts 'fai la quarta elementare'
when 10
  puts 'fai la quinta elementare'
else
  if eta > 10
  puts 'Vai alle medie?'
  else
    puts "Fai ancora l'asilo"
  end
end

Ho usato l'espressione condizionale case..when..end. È nella sua forma completa: if..when..else..end. Vediamo che succede in questo caso. Questa espressione è come una cassettiera con i cassetti numerati, ci sono cinque cassetti numerati 6,7,8,9,10 e un ultimo dove si mette tutto quello che non va negli altri. Qui ho usato anche una condizione if..else..end per diramare ulteriormente il test.

Eseguendolo potete vedere i risultati:

➜  source  ruby test3.rb 
Quanti anni hai?
2
Fai ancora l'asilo
➜  source  ruby test3.rb
Quanti anni hai?
6
fai la prima elementare
➜  source  ruby test3.rb
Quanti anni hai?
11
Vai alle medie?
➜  source  ruby test3.rb
Quanti anni hai?
10
fai la quinta elementare
➜  source  

L'espressione case..when..end è davvero molto utile, cose come queste ci sono i tutti i linguaggi di programmazione anche se a volte meno potenti. In Ruby potete valutare praticamente qualunque cosa.

Ecco una stringa:

case nome
  when 'pippo'
    puts 'ti chiami pippo'
  else
    puts 'non ti chiami pippo'
end

Simuliamo una condizione if..else..end:

test_booleano = true

case test_booleano
  when true
    puts 'vero'
  when false
    puts 'falso'
end

L'espressione case..when..end è utilissima e spesso mantiene il codice pulito e facilmente leggibile, provate e divertitevi.

Nelle espressioni condizionali possiamo fare più test insieme. Come? Con gli operatori logici:

  • or che si può scrivere spesso anche come ||
  • and che si può scrivere spesso anche come &&
  • not che si può scrivere spesso anche come !

La differenza tra quello scritto e quello a simbolo (and e &&) sta nel livello di precedenza di valutazione: la versione scritta ha una precedenza più bassa. Normalmente non ve ne curate, ma può causare a volte problemi subdoli. Nel dubbio è preferibile la versione simbolica.

if a >= 0 && a <= 9
  puts 'la variabile a è compresa tra 0 e 9'
end

Qui abbiamo detto a Ruby: se la variabile a è maggiore od uguale 0 e la variabile a è minore od uguale a 9, scrivi…

if nome == 'pippo' || nome == 'pluto'
  puts 'ti chiami pippo o pluto'
else
  puts 'il tuo nome è un altro'
end

Qui invece: se la variabile nome contiene la stringa pippo o la variabile nome contiene la stringa pluto, scrivi…

Si possono mettere più condizioni su uno stesso if..else..end, certo bisogna fare attenzione, molta attenzione. Se avete condizioni multiple, dato che hanno la stessa precedenza e vengono valutate da sinistra verso destra, a volte avrete bisogno delle parentesi per raggruppare le valutazioni. L'operatore not è di tipo unario e quindi si applica solo ad un operando: !true è uguale a false come non vero è uguale a falso. Gli altri invece si dicono binari perché si applicano a due operandi. Ne esiste anche uno terziario che serve per esprimere una condizione su una sola riga:

#questa riga è equivalente al test if..else..end
condizione ? puts('vera') : puts('falsa')

if condizione
  puts 'vera'
else
  puts 'falsa'
end

I due test sono equivalenti.

Ruby ha anche un'altra sintassi per i test negativi: unless..else..end. In pratica: se non è vero fai qualcosa altrimenti fai altro.

if a != 1
  puts 'a è diverso da 1'
end

unless a == 1
  puts 'a è diverso da 1'
end

I due test sono equivalenti. Il test unless vi assicuro che è davvero comodo, perché più spesso di quanto possiate credere è più chiaro e facilmente capibile il controllare che una cosa non sia vera.

3.4.6 Cicli, ripetere le cose da fare molte volte

I cicli ci permettono di ripete delle istruzioni più volte. Ruby ha alcuni tipi di cicli come la maggior parte dei linguaggi di programmazione.

  • while..end finché la condizione è vera fai qualcosa
  • until..end finché la condizione è falsa fai qualcosa
  • for..in..end per ogni valore all'interno di un insieme di valori fai qualcosa

È un po' come if e unless.

Ora vi dico una cosa, è abbastanza raro usare questi cicli in Ruby e lo vedrete più avanti.

Scrivete nel vostro editor il programma che segue, salvatelo come loop.rb ed eseguitelo:

puts 'Inserisci un numero'
numero = gets.chomp.to_i

while numero != 10
  puts 'il mumero è diverso dal numero segreto'
  puts 'Inserisci un numero'
  numero = gets.chomp.to_i
end 

puts 'hai trovato il numero segreto. Complimenti!'

Il ciclo eseguirà le istruzioni date dentro di lui finché il numero inserito non sarà 10.

Se adesso uso until invece di while:

puts 'Inserisci un numero'
numero = gets.chomp.to_i

until numero != 10
  puts 'il mumero è diverso dal numero segreto'
  puts 'Inserisci un numero'
  numero = gets.chomp.to_i
end 

puts 'hai trovato il numero segreto. Complimenti!'

Visto che viene valutata la falsità funzionerà al contrario.

Il ciclo for invece prenderà un valore da un insieme di valori assegnandolo ad una variabile fino ad esaurire tutti i valori. Sembra complicato ma non lo è molto.

numeri = [0,1,2,3,4,5,6,7,8,9]

for numero in numeri
  puts numero
end

Se lo eseguite come sapete:

➜  source  ruby loop2.rb
0
1
2
3
4
5
6
7
8
9
➜  source  

Il programma ha stampato tutti i numeri compresi nell'array chiamato numeri. Vediamo però subito cosa è un array (tipo Array). È una collezione di elementi, in questo caso numeri. Non l'ho chiamato insieme perché esiste nella libreria Ruby un tipo Set che serve apposta per gli insiemi matematici. È più corretto chiamare questi tipi di dati collezioni e noi vogliamo imparare il modo più corretto possibile di nominare le cose.

Agli elementi contenuti in una collezione si può accedere direttamente se si vuole:

array = [1,2,3,4,5,6]

primo_elemento = array[0]
secondo_elemento = array[1]

Vi si accede tramite quello che si chiama indice dell'array. Gli array sono indicizzati a partire da zero, questo lo dovete ricordare bene. Ci sono anche i metodi first e last che come dicono i loro nomi accedono al primo e l'ultimo elemento. A volte ci interessa anche sapere quanti elemeti ci sono nell'array e quindi abbiamo size.

Qualcuno furbo ha capito l'inghippo della indicizzazione da zero?

irb(main):003:0> array = [0,1,2,3,4,5,6,7,8,9]
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
irb(main):004:0> array.first
=> 0
irb(main):005:0> array.last
=> 9
irb(main):006:0> array.size
=> 10
irb(main):007:0> array[0]
=> 0
irb(main):008:0> array[3]
=> 3
irb(main):009:0> array[array.size]
=> nil
irb(main):010:0> array[array.size - 1]
=> 9
irb(main):011:0> 

Capito?

Una nota: il valore nil è un valore particolare che ha un significato analogo al latino nihil cioè il nulla. È il valore che si ha quando si leggono cose che non esistono: variabili, valori…

Insomma un array è come una scatola con gli scompartimenti ed ogni scompartimento può contenere qualsiasi tipo di dato supportato da Ruby: numeri, stringhe, array… Non solo, un array in Ruby contrariamente ad altri linguaggi può mischiare i tipi.

array = [1, 2, "pippo", 3, "pluto", ['a','b','c']]

Immaginate adesso le odiose tabelline, quelle che la maestra vi chiede continuamente… Bene, la tabella pitagorica è un array (in questo caso a due dimensioni).

Qualcuno furbo ha già capito come far fare una tabella pitagorica a Ruby?

numeri = [1,2,3,4,5,6,7,8,9,10]

for numero in numeri
  riga = ''
  for moltiplicatore in numeri
    valore = numero * moltiplicatore
    if valore < 10
      separatore = '  '
    else
      separatore = ' '
    end
    riga = riga + valore.to_s + separatore
  end
  puts riga
end

Eseguitelo…

➜  source  ruby tabellina.rb 
1  2  3  4  5  6  7  8  9  10 
2  4  6  8  10 12 14 16 18 20 
3  6  9  12 15 18 21 24 27 30 
4  8  12 16 20 24 28 32 36 40 
5  10 15 20 25 30 35 40 45 50 
6  12 18 24 30 36 42 48 54 60 
7  14 21 28 35 42 49 56 63 70 
8  16 24 32 40 48 56 64 72 80 
9  18 27 36 45 54 63 72 81 90 
10 20 30 40 50 60 70 80 90 100 
➜  source  

Che ne dite? Va bene? (Si potrebbe anche fare meglio, ma per ora va bene così). Guardate bene il codice e cercate di capire, avete tutte le informazioni per farlo.

Come vi ho già detto, i cicli in Ruby non sono molto usati perché ha anche altri modi di fare la stessa cosa in maniera più compatta e leggibile.

numeri = [1,2,3,4,5,6,7,8,9,10]

tabella = numeri.map do |numero|
  riga = numeri.map do |moltiplicatore|
    valore = numero * moltiplicatore
    separatore = valore < 10 ? '  ' : ' '
    valore.to_s + separatore
  end
  riga.join
end
puts tabella.join("\n")

Questo ne è un esempio, anche se non pare adesso quando sarete più bravi vi sembrerà meglio di quello sopra:

numeri = [1,2,3,4,5,6,7,8,9,10]

puts (numeri.map { |numero|
        (numeri.map { |moltiplicatore|
          valore = numero * moltiplicatore
          separatore = valore < 10 ? '  ' : ' '
          valore.to_s + separatore
        }).join
      }).join("\n")

Per arrivare magari a questo:

numeri = [1,2,3,4,5,6,7,8,9,10]
puts (numeri.map { |numero|
        (numeri.map { |moltiplicatore| 
          (valore = numero * moltiplicatore).to_s + (valore < 10 ? '  ' : ' ') 
        }).join
      }).join("\n")

Forse troverete anche altri modi… Negli ultimi due esempi, ci sono delle cose da notare che fanno di Ruby un linguaggio particolare (detto funzionale). Il concetto non è semplice.

Cercate di stare attenti, questa cosa che dirò adesso non è facile. Le istruzioni, le variabili ed i metodi che usiamo in Ruby vivono dentro un contesto. Cosa è? Pensate a casa vostra, quello è il vostro contesto. Pensate alla scuola, l'aula. L'aula è il vostro contesto e cioè la zona chiusa dove state e dove fate qualcosa. In Ruby si possono fare queste stanze in vari modi: con le parentesi per esempio ma anche con parole chiave (keywords) particolari come do..end.

Le keywords do..end corrispondono a {..} e definiscono quello che viene chiamato blocco ma anche closure (chiusura).

#prima forma
do |parametro|
  ...
end

seconda forma
{ |parametro|
  ...
}

Queste forme sono closure con un valore in entrata.

Vediamo il metodo each che è un metodo del tipo Array (ma anche altri tipi lo hanno):

[1,2,3,4,5,6,7,8,9,10].each {|numero|
  puts numero
}

for numero in [1,2,3,4,5,6,7,8,9,10]
  puts numero
end

Le due forme sono equivalenti, cioè portano allo stesso risultato e stamperanno in output i valori contenuti nell'array. La differenza è di tipo paradigmatico. Vedo già le vostre facce sconvolte. - E che vuol dire? -.

Un paradigma è un modo, un metodo per fare qualcosa. Delle regole. Un paradigma di calcolo è quello che usate per fare le divisioni o altri calcoli per esempio. Si potrebbero fare in molti modi e voi ne usate uno di questi. Nei linguaggi di programmazione ci sono molti modi di fare le cose, molti paradigmi di programmazione.

Se noi scriviamo con il for..in..end usiamo quello che viene detto paradigma imperativo, se usiamo il metodo each quello detto paradigma funzionale. Ruby ci permette di farlo nei due modi e ci lascia liberi di usare quello che ci piace di più. Vedrete poi che quello funzionale è generalmente migliore, ma lo capirete da soli.

Insomma, each è un metodo del tipo array che come parametro in input prende una closure o una lambda o un oggetto Proc passando nel parametro di questi il valore di ogni elemento dell'array su cui è chiamato.

Bene, ora ci vuole un mese di vacanza per riposarci!

A parte le chiacchiere e le definizioni da secchioni, una volta usata è più semplice del previsto. Se guardate il codice sopra, immaginate che dentro numero ci vada finire dentro di volta in volta 1 poi 2 poi 3 poi 4 e così via fino a 10 che è l'ultimo elemento della collezione. Nel codice dentro la closure potere usare poi numero e fare quello che volete. Qui lo stampiamo in output.

Negli esempi io ho usato il metodo map, che è simile ad each, ma mentre each invoca la closure per ogni elemento della collezione senza fare altro, map cosa fa… restituisce un'altra collezione coi dentro i risultati della elaborazione della closure. Nell'esempio poi trasformo una collezione in una stringa usando il metodo join (unisci), un metodo che prende tutti gli elementi della collezione e li concatena come una stringa.

Questo modo di ciclare (brutta parola e si dice solo qui… è come le parolacce) si chiama più giustamente iterazione e questi metodi si chiamano iteratori. Un altro metodo iteratore è per esempio times che però è dei numeri e non degli array:

10.times do 
  puts "ciao!"
end

Verrà scritto in output dieci volte ciao. Usare gli iteratori è molto importante e rende il codice meglio organizzabile e leggibile.

Questa è la lista dei metodi disponibili per il tipo Array:

irb(main):001:0> Array.methods.sort
=> [:!, :!=, :!~, :<, :<=, :<=>, :==, :===, :=~, :>, :>=, :[], :__id__, :__send__, :allocate, :ancestors, :autoload, :autoload?, :class, :class_eval, :class_exec, :class_variable_defined?, :class_variable_get, :class_variable_set, :class_variables, :clone, :const_defined?, :const_get, :const_missing, :const_set, :constants, :define_singleton_method, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :include, :include?, :included_modules, :inspect, :instance_eval, :instance_exec, :instance_method, :instance_methods, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :kind_of?, :method, :method_defined?, :methods, :module_eval, :module_exec, :name, :new, :nil?, :object_id, :prepend, :private_class_method, :private_constant, :private_instance_methods, :private_method_defined?, :private_methods, :protected_instance_methods, :protected_method_defined?, :protected_methods, :public_class_method, :public_constant, :public_instance_method, :public_instance_methods, :public_method, :public_method_defined?, :public_methods, :public_send, :remove_class_variable, :remove_instance_variable, :respond_to?, :send, :singleton_class, :singleton_class?, :singleton_method, :singleton_methods, :superclass, :taint, :tainted?, :tap, :to_enum, :to_s, :trust, :try_convert, :untaint, :untrust, :untrusted?]
irb(main):002:0> 

Ho fatto la stessa cosa fatta indietro con Fixnum, ricordate? Ho qui però usato un nuovo metodo: sort. Il metodo sort mette in ordine un array.

3.4.7 Scriviamo i nostri metodi, altrimenti a che serve?

Fino ad adesso abbiamo visto a grandi linee cosa sono i metodi e ne abbiamo usati alcuni. I metodi, si possono definire come le variabili. Altrimenti potremmo anche andare a coltivale le cipolle (che sono buone e le adoro anche crude nell'insalata).

Come si fa a definire un metodo? Per prima cosa decidiamo un bel nome, un nome importante… Soprattutto un nome che dica cosa fa, per ricordarlo meglio.

def somma(x, y)
  x + y
end

Abbiamo definito un metodo che prende due valori in input, x ed y, che si chiama somma. Lo possiamo usare così:

def somma(x, y)
  x + y
end

risultato = somma(2, 3)

puts risultato

Fate nell'editor, salvate come metodi.rb ed eseguite. Vedrete il risultato di 2 + 3. Invece di assegnare il valore di ritorno del metodo ad una variabile, in questo caso avreste anche potuto scrivere:

puts somma(2, 3)

Ricordate? puts è un metodo, quindi vuol dire che noi possiamo passare un metodo ad un altro metodo come suo parametro in input.

puts somma(somma(2, 3), 5)

Che farà? Quanto farà? Chi lo sa? Al primo che risponde niente compiti.

I metodi, come abbiamo già visto, servono per scrivere delle istruzioni che poi possiamo riutilizzare. Pensate se ogni volta dovessimo riscrivere le stesse cose. Programmare è spesso un lavoro ripetitivo e potenzialmente si scrivono le stesse cose centinaia se non migliaia di volte. Senza i metodi (almeno quelli) saremmo perduti (anche se pensate esistono linguaggi che non li hanno).

I metodi sono raggruppati in librerie generalmente, anche Ruby ha le sue librerie. Insomma i metodi sono come delle parole o meglio dei capitoli dentro dei libri, che sono sugli scaffali di una libreria. È importante ricordare questo perché vedremo in seguito che per chiamare un metodo dobbiamo trovarlo come un libro nello scaffale: scaffale_destro::libro_primo.capitolo_secondo.

Trasformiamo il codice per scrivere la tabella pitagorica come metodo:

# prepara una tabella (array bidimensionale)
# con i valori
def tabella_pitagorica(numeri)
  numeri.map { |numero|
    numeri.map { |moltiplicatore| 
      numero * moltiplicatore
    }
  }
end

# stampa un array bidimensionale
def stampa_tabella(tabella)
  puts (tabella.map { |riga| 
    (riga.map {|numero| 
      numero.to_s + (numero < 10 ? '  ' : ' ')
    }).join
  }).join("\n")
end

stampa_tabella(tabella_pitagorica([1,2,3,4,5,6,7,8,9,10]))

Al solito salviamo, come metodi1.rb ed eseguiamo.

➜  source  ruby metodi2.rb
1  2  3  4  5  6  7  8  9  10 
2  4  6  8  10 12 14 16 18 20 
3  6  9  12 15 18 21 24 27 30 
4  8  12 16 20 24 28 32 36 40 
5  10 15 20 25 30 35 40 45 50 
6  12 18 24 30 36 42 48 54 60 
7  14 21 28 35 42 49 56 63 70 
8  16 24 32 40 48 56 64 72 80 
9  18 27 36 45 54 63 72 81 90 
10 20 30 40 50 60 70 80 90 100 
➜  source 

Come vedete il risultato è quello. Adesso abbiamo un metodo chiamato tabella_pitagorica che prenderà in input come parametro un array di numeri e costruirà un array di array di numeri.

[[1,2,3,4,5,6,7,8,9],[2,4,6,8,10,12,14,16,18,20], [3,6,9,12,15,18,21,24,27,30], ...]

L'altro metodo, stampa_tabella, prende in entrata un array bidimensionale (la tabella coi valori) e lo stampa in output. La cosa interessante è che stampa_tabella stampa in quel modo, qualunque array bidimensionale e non solo la nostra tabella pitagorica.

# stampa un array bidimensionale
def stampa_tabella(tabella)
  puts (tabella.map { |riga| 
    (riga.map {|numero| 
      numero.to_s + (numero < 10 ? '  ' : ' ')
    }).join
  }).join("\n")
end

stampa_tabella([[1,10,30,4,50,6,7,8,9,0,9,0],[2,4,6,81,17,12,14,1,1,2], [3,6,9,12,15,18,21,24,27,30]])

Salvate come metodi3.rb ed eseguite:

➜  source  ruby metodi3.rb
1  10 30 4  50 6  7  8  9  0  9  0  
2  4  6  81 17 12 14 1  1  2  
3  6  9  12 15 18 21 24 27 30 
➜  source  

I metodi possono avere più parametri ma come consiglio limitatevi al meno possibile.

3.5 Le cose serie

Ora cominceremo ad affrontare alcune delle caratteristiche di Ruby che ne fanno un linguaggio agile moderno.

3.5.1 Raggruppiamo i metodi in moduli

Ruby è un linguaggio estremamente modularizzabile il che significa che ha numerosi modi di riutilizzare le cose già scritte. Questo ne fa un linguaggio molto adatto al lavoro in collaborazione, cioè tra più persone che lavorano a parti diverse del codice.

Uno dei meccanismi di raggruppamento sono i moduli:

module Pippo

  def dice(cosa)
    puts cosa
  end

end

I moduli sono dei contesti dove i metodi (ed altre cose) vivono. Prima però di usare i metodi di un modulo, dobbiamo includerlo. Avete presente quando un amichetto viene a casa vostra e giocate insieme? Uguale. Dovete prima invitare il modulo nel vostro contesto. A casa vostra, insomma.

module Pippo

  def dice(cosa)
    puts "Pippo dice: " + cosa
  end

end

include Pippo

dice("ciao")

Salvate come moduli.rb ed eseguite, vedrete che scriverà: Pippo dice ciao. Avete invitato il modulo Pippo nel vostro contesto con la parola chiave (keyword) include. Da lì in poi potete usare il metodo dice semplicemente. Se non includete il modulo, Ruby si lamenterà dicendo che non trova il metodo ciao

➜  source  ruby moduli.rb
moduli.rb:20:in `<main>': undefined method `dice' for main:Object (NoMethodError)
➜  source 

Includere un modulo significa copiare i metodi del modulo nel contesto dove si include, questo può sembrare difficile ma non lo è (non molto almeno) ed ha delle conseguenze.

module Pippo

  def dice(cosa)
    puts "Pippo dice: " + cosa
  end

end

module Pluto

  def dice(cosa)
    puts "Pluto dice: " + cosa
  end

end

include Pippo
include Pluto

dice("ciao")

Salvate come moduli2.rb ed eseguite:

➜  source  ruby moduli2.rb
Pluto dice: ciao
➜  source  

Il problema è: quale metodo dice viene chiamato? Come vedete è quello di Pluto perché il modulo Pluto è incluso dopo il modulo Pippo. Ci sono varie implicazioni in questo comprese delle cose strane:

module Pippo

  def dice(cosa)
    puts "Pippo dice: " + cosa
  end

end

module Pluto

  def dice(cosa)
    puts "Pluto dice: " + cosa
  end

end

include Pippo
include Pluto

Pippo.dice("ciao")
Pippo::dice("ciao")

Salvate come moduli3.rb ed eseguite:

➜  source  ruby moduli3.rb
Pluto dice: ciao
Pluto dice: ciao
➜  source  

Come si può vedere anche usando gli operatori di visibilità di Ruby :: o . non si riesce ad invocare il dice racchiuso dentro Pippo. Questo è spiegabile ma non adesso. Per ora ricordate che: se includete un modulo i metodi di questo sono copiati dove li avete inclusi e che i metodi con lo stesso nome e lista di prametri si sovrascrivono.

Adesso vediamo come rendere un modulo una libreria.

Scrivete e salvate questi due file, il primo chiamatelo pippo.rb ed il secondo moduli4.rb.

module Pippo

  def dice(cosa)
    puts "Pippo dice: " + cosa
  end

end

require './pippo'

include Pippo

dice("ciao")

Eseguite moduli4.rb:

➜  source  ruby moduli4.rb
Pippo dice: ciao
➜  source 

Abbiamo trasformato il modulo Pippo in una libreria. Potete mettere quello che volete dentro il file della libreria e chiamarlo come volete. Lo dovete prima richiedere con la keyword require ed il percorso nel file system del file. La sintassi del percorso segue lo standard Unix quindi non avete come su Microsoft Windowstm le barre rovesciate \ ma invece avete le barre normali /.

In questo caso, require './pippo', significa: richiedi il file pippo.rb che si trova nella cartella corrente. Una volta richiesto, il file potrà essere usato come se il suo codice fosse scritto direttamente.

Il dividere un programma in più file, consente di riutilizzare il codice scritto in precedenza da noi o da altri e di averne anche una organizzazione spaziale (e non parlo di astronavi).

3.5.2 Le classi (non quelle della scuola, o forse si?)

Cominciamo a vedere le classi, questi oggetti misteriosi. Non ha caso ho detto classi e oggetti.

Ruby è un linguaggio di programmazione Object Oriented, l'ho già detto prima. Ricordate?

Vuol dire: linguaggio di programmazione orientato agli oggetti. Che vuol dire: un linguaggio di programmazione in cui gli oggetti sono la cosa più importante. Che vuol dire: un linguaggio di programmazione dove si usano delle descrizioni degli oggetti, chiamate classi, per modellare il dominio del problema. Continuo? - Sempre più difficile, Signore e Signori! -

Facciamola semplice e partiamo da qualche esempio.

Intorno a noi ci sono tante cose, pensateci bene. Imparare a programmare i computer fa bene anche perché insegna a ragionare e ad affrontare i problemi in maniera analitica. Insomma, intorno a noi abbiamo tante cose: viviamo dentro delle case, guardiamo il televisore, ascoltiamo la musica col giradischi (che voi forse non sapete nemmeno cosa sia, ma io sono vecchio e lo so), camminiamo con le scarpe, ci mettiamo i pantaloni e ci sediamo sulla sedia, leggiamo libri… Potrei continuare all'infinito.

Intorno a noi ci sono oggetti. Questi oggetti servono a qualcosa, fanno qualcosa o subiscono qualcosa. Gli oggetti possono essere formati da altri oggetti, ricordate quando prima ho detto dei mattoncini Lego(tm)? Il Meccano(tm), quando ero piccolo c'era questo, oggetti in metallo, viti e bulloni, motori elettrici… Ci ho costruito tante cose. Chi di voi è uno smontatore professionista? Io da piccolo smontavo praticamente tutto: volevo vedere come funzionasse dentro.

Con i linguaggi di programmazione avete gli strumenti per scatenare la curiosità, se volete. Ritorniamo ai nostri oggetti ed alla definizione di un linguaggio di programmazione orientato agli oggetti (come è Ruby, che però non è solo questo):

  • è un linguaggio che permette di descrivere il dominio del problema definendo gli oggetti che lo compongono e le loro interazioni

Il dominio del problema significa solo quello che volete fare, perché usare delle parole difficili? Primo è perché vogliamo imparare le parole giuste, secondo perché, in realtà, c'è di più di quello che volete fare. Per adesso però, va bene così.

Per fare quello che volete fare dovete analizzare tutti i piccoli pezzi che compongono o servono per fare quello che volete fare. Tra un po' comincio con gli scioglilingua…

Ma cosa volete fare?.

Pensate alla macchina, che poi si dice automobile… Anche se è una macchina. Elenchiamo i pezzi che la compongono: sedili, sportelli, volante, ruote, vetri, ingranaggi, motore, viti, bulloni e chi più ne ha ne metta

Ho puntualizzato che l'automobile è una macchina e vedrete che questa affermazione banale, così semplice, ha una sua ragione di esistere.

Cominciamo a descrivere meglio una automobile e facciamolo in Ruby.

class Automobile

end

Abbiamo definito una classe e cioè una classe di oggetti. La classe (class) è la descrizione dell'oggetto con le sue proprietà o attributi (gli oggetti che la compongono) ed i metodi che sono il come interagisce col mondo esterno, cosa fa o cosa subisce.

L'automobile ha le ruote prima di tutto:

class Automobile

  @ruote = 4

end

@ruote si chiama variabile di istanza. È una variabile come le abbiamo già viste, ma vive dentro un oggetto che sarebbe una istanza di una classe.
Le variabili di istanza cominciano per @.

Ricapitoliamo:

  • la classe descrive l'oggetto nella sua forma e funzione
  • l'istanza rende viva la classe come oggetto

Una variabile di istanza non è ancora un attributo, anche se ancora non sappiamo cosa significhi un attributo. Per farla semplice, diciamo che una classe ha delle parti nascoste e delle parti visibili, l'automobile ha di visibile la carrozzeria ma non il motore. Per vedere quello dovete aprire il cofano, guardare dentro. È la stessa cosa, una classe in Ruby ha delle parti nascoste e delle parti visibili: @ruote è ancora nascosta. Direi anche di lasciarla nascosta, mica vogliamo che mentre siamo in corsa a duecento chilometri all'ora verso una curva ci cambino il numero delle ruote? Se di punto in bianco diventano tre? O due? o Niente?

Lasciamola nascosta che è meglio (citazione dai Puffi). Però, il numero delle persone potrebbe cambiare no?

Aggungiamo le persone, le auto portano le persone:

class Automobile

  @ruote = 4
  attr_accessor :persone

end

Ora noi abbiamo che persone è accessibile e si vede dall'esterno dell'oggetto.

irb(main):001:0> class Automobile
irb(main):002:1> @ruote = 4
irb(main):003:1> attr_accessor :persone
irb(main):004:1> end
=> nil
irb(main):005:0> Auto = Automobile.new
=> #<Automobile:0x007f8995f94a90>
irb(main):006:0> Auto.persone = 4
=> 4
irb(main):007:0> Auto.persone
=> 4
irb(main):008:0> 

Ho rifatto il codice dentro irb per farvi vedere? Capito? L'attributo persone è visibile dal'esterno dell'oggetto. Analizziamo il codice meglio.

Dopo aver definito la classe Automobile ho costruito l'oggetto Automobile con il metodo new (nuovo). Si dice che ho istanziato la classe. L'oggetto appena creato l'ho immagazzinato dentro una variabile che ho chiamato Auto.

Cominciamo a capire cosa è la classe? La classe è come il progetto di un oggetto, che poi va costruito. Infatti nel gergo dei linguaggi di programmazione orientati agli oggetti, metodi come new sono chiamati costruttori (anche se con Ruby è impreciso e sarebbe meglio chiamarlo metodo istanziatore).

irb(main):005:0> Auto = Automobile.new
=> #<Automobile:0x007f8995f94a90>

Quello che vedete sotto ad Auto = Automobile.new è il nome dell'istanza della classe Automobile che ho appena creato. Il nome vero, ma noi ci riferiremo a questa con Auto.

Adesso cerco di cambiare il numero delle persone e quello delle ruote:

irb(main):009:0> Auto.persone
=> 4
irb(main):010:0> Auto.persone = 5
=> 5
irb(main):011:0> Auto.ruote = 2
NoMethodError: undefined method `ruote=' for #<Automobile:0x007f8995f94a90 @persone=5>
  from (irb):11
  from /home/nissl/bin/ruby-2.1/bin/irb:11:in `<main>'
irb(main):012:0> 

Sono riuscito a farlo per le persone ma non per le ruote. L'attributo persone è visibile all'esterno e si può cambiare: è accessibile in lettura e scrittura; ruote no.

Ruby però, in certe cose è davvero strano ed è nella sua natura esserlo. In Ruby tutto è un oggetto.

Non vorrei complicarvi la vita troppo, e la faccio breve con un esempio:

irb(main):017:0> Auto.instance_variables
=> [:@persone]
irb(main):018:0> Automobile.instance_variables
=> [:@ruote, :@persone]
irb(main):019:0> 

Ho invocato il metodo instance_variables (che mi elenca dentro un array le variabili di istanza di un oggetto) sull'oggetto Auto e sulla classe Automobile e… Auto ha una variabile di istanza mentre Automobile ne ha due. Automobile ha due variabili di istanza? Finora vi ho detto che Automobile non era una istanza ma una classe! Vi ho perso in giro? No, non l'ho fatto ma non sono stato preciso. In Ruby, tutto è un oggetto, anche le classi. Questo porta ad interessanti implicazioni e possibilità del linguaggio che però vanno oltre lo scopo di questo piccolo corso.

Sappiate questo: se vogliamo utilizzare una variabile di istanza dentro una istanza dobbiamo scriverla in un altro modo.

class Automobile  

  attr_reader :ruote
  attr_accessor :persone

  def initialize
    @ruote = 4
  end

end

Ho aggiunto attr_reader ed un metodo importantissimo: initialize. Il metodo inizialize viene invocato automaticamente da Ruby quando usiamo il metodo costruttore new. Il nome stesso spiega cosa fa: inizializza l'istanza; cioè fa qualcosa mentre l'oggetto è preparato per essere usato. Le variabili di istanza di un oggetto devono essere definite e dichiarate qui dentro se le vogliamo avere disponibili alla creazione. La dichiarazione attr_reader :ruote dice che la variabile di istanza @ruote sarà solo disponibile in lettura.

Questi i dichiaratori di accesso delle variabili di istanza:

  • attr_reader accesso in sola lettura
  • attr_writer accesso in sola scrittura
  • attr_accessor accesso in lettura e scrittura

Questi dichiaratori, sono dei metodi di convenienza se non usassi attr_accessor dovrei scrivere così:

class Automobile  

  def initialize
    @ruote = 4
  end

  def persone
    @persone
  end

  def persone=(numero)
    @persone = numero
  end

  def ruote
    @ruote
  end

end

Come vedete ho scritto di più di prima ed il codice è meno leggibile e più complicato. Ruby ha molti metodi di convenienza (detti helpers) quindi usateli.

Cerchiamo di migliorare la nostra Automobile. In fondo, scusate cosa è una Automobile? È un veicolo a motore… Invece un veicolo a motore non è un generico veicolo? Un'altra cosa interessante dei linguaggi di programmazione orientati agli oggetti è che supportano l'ereditarietà. Avete presente voi ed i vostri genitori? A chi assomigliate? Da chi avete ereditato il naso o gli occhi?

Partendo da un generico Veicolo andiamo verso un Veicolo a motore e poi ad una Automobile.

  • Veicolo -> VeicoloAMotore -> Automobile

Scrivete e salvate questo file come automobile.rb.

class Veicolo
end

class VeicoloAMotore < Veicolo

  attr_reader :motore

  def initialize
    @motore = true
  end

end

class VeicoloARuoteConMotore < VeicoloAMotore

  attr_reader :ruote

  def initialize(numero_ruote = 4)
    super()
    @ruote = numero_ruote
  end

end

class Automobile < VeicoloARuoteConMotore

  attr_accessor :persone

  def initialize
    super(4)
    @persone = 0
  end

end

Auto = Automobile.new

Auto.persone = 5

puts "Auto ha il motore: " + (Auto.motore ? "si" : "no")
puts "Ospita quante persone? " + Auto.persone.to_s
puts "Quante ruote? " + Auto.ruote.to_s
puts
puts "Variabili di istanza sono:"
puts Auto.instance_variables

Se lo fate girare:

➜  source  ruby automobile.rb
Auto ha il motore: si
Ospita quante persone? 5
Quante ruote? 4

Variabili di istanza sono:
@motore
@ruote
@persone
➜  source  

Abbiamo creato la nostra prima gerarchia di classi.

Come vedete: Veicolo -> VeicoloAMotore -> VeicoloARuoteConMotore -> Automobile.

Ogni classe ha un initialize che noi dovremmo richiamare nella classe derivata per essere sicuri che il genitore sia inizializzato a dovere. Questo si fa con super. Il metodo super si assicura di richiamare il metodo inizialize del padre, infatti noi dobbiamo scriverlo e passargli eventuali parametri. Il suo funzionamento è abbastanza magico nel senso che i parametri dell'initialize del figlio vengo passati automaticamente a super. Per questo motivo nella classe VeicoloARuoteConMotore è scritto super(), per evitare che a inizialize del genitore venga passato un parametro che non è richiesto. Questo sarebbe stato l'errore:

➜  source  ruby automobile.rb
automobile.rb:8:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
  from automobile.rb:19:in `initialize'
  from automobile.rb:30:in `initialize'
  from automobile.rb:36:in `new'
  from automobile.rb:36:in `<main>'
➜  source 

Dimenticavo, come forse avete notato il metodo initialize della classe VeicoloARuoteConMotore è così: def initialize(numero_ruote = 4). In Ruby i parametri possono avere dei valori già impostati (di default) e ciò permette di ometterli quando si invoca un metodo. Non abusate di questa facilitazione può essere molto pericolosa.

So di essere stato contorto ma non è semplice da spiegare. Dovete riflettere bene su questo e cercare di capire. Ruby ha, come già detto altre volte, delle cose che sembrano a prima vista strane, ma vedrete che invece sono la sua forza.

Cambiamo le classi però, che così non vanno tanto bene:

class Veicolo
end

class VeicoloAMotore < Veicolo

  @@motore = true

  def motore
    @@motore
  end

end

class VeicoloARuoteConMotore < VeicoloAMotore

  def initialize(numero_ruote = 4)
    @@ruote = numero_ruote
  end

  def ruote
    @@ruote
  end

end

class Automobile < VeicoloARuoteConMotore

  attr_accessor :persone

  def initialize
    super(4)
    @persone = 0
  end

end

Auto = Automobile.new

Auto.persone = 5

puts "Auto ha il motore: " + (Auto.motore ? "si" : "no")
puts "Ospita quante persone? " + Auto.persone.to_s
puts "Quante ruote? " + Auto.ruote.to_s
puts
puts "Variabili di istanza sono:"
puts Auto.instance_variables

Ho scritto delle variabili con due @ (@@). Queste sono variabili di classe e si propagano per tutta la gerarchia degli oggetti. La differenza tra le class variables e le instance variables è che le seconde nelle istanze figlie sono delle copie, la prime le stesse.

Esempio: due bambini comprano due cacciaviti sonici del Doctor Who, oppure ne comprano uno solo e se lo passano. Nel primo caso, se uno si rompe, è solo quel bambino a piangere. Nel secondo piangono tutti e due.

Chiaro?

Classi ne abbiamo molte in Ruby, già di suo. Array è una classe, Fixnum un'altra, String. Abbiamo poi una classe Time per gestire il tempo o Hash che è importantissima. Ce ne sono davvero tante:

Ruby ha una libreria molto estesa per fare davvero molte cose. Oltre alla sua, ha un sistema di gestione delle librerie che si chiama: RubyGems, https://rubygems.org/; dove sono reperibili migliaia di ulteriori librerie. Oltre a questo ne scriverete anche di vostre, no?

3.5.3 Blocks e Procs, che non è il Rock'n'Roll ma ci si avvicina.

Ruby ha delle fantastiche caratteristiche, questa è una di quelle. Li abbiamo già visti, nei metodi each o map che abbiamo usato nei cicli. Possiamo scrivere metodi come quelli. Blocks, Procs, Closure e lambda, sono sostanzialmente simili e scritti in maniera simile. Le differenze a volte sono solo di fino come si dice.

Vediamo come si scrive una Proc:

ciao = Proc.new do
  puts "ciao"
end 

ciao.call
ciao.call
ciao.call

Questo scriverà tre volte ciao.

ciao
ciao
ciao

Una Proc può prendere parametri:

ciao = Proc.new do |a_chi|
  puts "ciao"
end 

ciao.call('Mondo')
ciao.call('Carlo')
ciao.call('Camilla')

Il suo output sarà:

ciao Mondo
ciao Carlo
ciao Camilla

La cosa fantastica delle Proc è che possiamo passarle come valori dei parametri e restituirle come valori dai metodi. Questo è un concetto complesso che a prima vista sembra non portare benefici ma non è così. È una caratteristi importante che fa di Ruby anche un linguaggio funzionale oltre che orientato agli oggetti, pur con alcune limitazioni.

Questo frammento definisce un metodo che prende come parametro una Proc:

ciao = Proc.new do
  "ciao!"
end

buongiorno = Proc.new do
  "buongiorno!"
end

def saluta_con_un(proc)
  puts "Ti saluto con un #{proc.call}"
end

saluta_con_un(ciao)
saluta_con_un(buongiorno)

In questo esempio ho usato un nuovo modo per concatenare le stringhe detto interpolazione. Funziona con le stringhe tra doppi apici (quelle in apici singoli non vanno bene) e praticamente il codice compreso tra #{…} viene valutato ed il risultato sostituito in quel punto. Ci permette di costruire stringhe con elementi da valutare lì per lì.

Il suo output sarà:

Ti saluto con un ciao!
Ti saluto con un buongiorno!

La Proc è stata passata come parametro ed invocata, questo perché la Proc è un valore, è un oggetto a tutti gli effetti. Un oggetto che contiene del codice.

ciao = lambda do
  "ciao!"
end

buongiorno = Proc.new do
  "buongiorno!"
end

def saluta_con_un(proc)
  puts "Ti saluto con un #{proc.call}"
end

saluta_con_un(ciao)
saluta_con_un(buongiorno)

Qui ho usato una lambda, Proc e lambda sono molto simili, la sintassi è praticamente identica. Ci sono differenze molto sottili di funzionamento che però vanno oltre lo scopo di questo minicorso.

Per finire un esempio di un metodo che prende come parametro un Block:

def saluta(persone, &saluto)
  persone.each { |nome|
    salut = yield saluto
    puts "#{ nome } #{ salut }"
  }
end

saluta(['Marco', 'Giovanni', 'Benedetta']) {
  "ciao"
}

Scriverà:

Marco ciao
Giovanni ciao
Benedetta ciao

Queste possibilità di Ruby sono solo state scalfite. Sono molto potenti.

3.5.4 Il mondo esterno

Fino ad adesso non abbiamo fatto altro che stare comodamente in casa, ma ogni tanto bisognerà uscire e parlare con la gente di fuori e magari non dimenticarsi quello che si è fatto. Uno dei vari modi per uscire all'aperto è leggere e scrivere file.

Lo avete fatto anche voi con il vostro editor, avete creato dei file sul disco del vostro computer e li avete letti, eseguiti con Ruby.

Ruby, come molti linguaggi ha la possibilità di aprire file, scrivere o leggere nei o dai file, chiudere i file. Non è un caso che abbia scritto: aprire, leggere o scrivere, chiudere; sono queste le fasi che servono per gestire i file.

Per fare questo abbiamo una classe che, ovviamente, si chiama File. File non fa solo quello, ha anche altri metodi che servono per esempio per avere informazioni sui file ma anche per gestirne i percorsi.

Un percorso serve per ritrovare un file nel disco del computer. Ci sono diverse nomenclature in base al sistema operativo che si sta usando e questo potrebbe rendere un po' complicato il gestirle.

Un percorso è formato da una serie di nomi di cartelle divisi da un separatore (qui è la differenza dei vari sistemi operativi) ed un nome di file.

/home/nissl/Documenti/Ruby/il_mio_file.txt

Questo percorso è di tipo Unix.

c:\Documenti\Ruby\il_mio_file.txt

Questo si usa con Microsoft Windowstm.

Senza entrare nelle differenze complicate, ne vediamo subito una e cioè la differenza di separatore (slash e backslash). Fortunatamente Ruby, che è nato sui sistemi di tipo Unix, considera la forma con la barra normale (slash) / la forma corretta e quindi ragiona in questo modo facendolo fa anche su Windowstm.

Quindi:

c:/Documenti/Ruby/il_mio_file.txt

Per Ruby questo è corretto e lui troverà il file. Per il sistema operativo no però e questo potrebbe avere delle conseguenze in certi casi.

Vediamo come si apre un file:

file = File.open('miofile.txt', 'r')

In questo modo, ho aperto un file in sola lettura, vuol dire che potrò leggerne il contenuto ma non lo potrò scrivere.

Ci sono vari parametri di permesso per i file e come si può vedere si indicano nel secondo valore dato al metodo mentre il primo è il nome del file.

  • r il file è aperto in sola lettura dal suo inizio.
  • r+ il file è aperto in lettura e scrittura dal suo inizio.
  • w il file è aperto in sola scrittura e tronca il file a zero, cioè elimina il contenuto del file o ne crea uno se questo non esiste.
  • w+ il file è aperto in lettura e scrittura e tronca il file a zero, cioè elimina il contenuto del file o ne crea uno se questo non esiste.
  • a il file è aperto in sola scrittura e aggiunge alla fine del file, appende.
  • a+ Il file è aperto in lettura e scrittura e aggiunge alla fine del file, appende.

Queste sono le possibilità e l'uso dipende da quello che vogliamo fare con il file. Per esempio, se vogliamo solo leggere un file dobbiamo aprirlo in sola lettura per non rischiare di scriverci qualcosa inavvertitamente.

Esistono altre impostazioni da passare al metodo open o new oltre a queste, ma si rimanda alla documentazione ufficiale.

Ricordate sempre: i file sono preziosi e vanno gestiti in maniera adeguata.

Come ho accennato i file devono essere: aperti, letti o scritti, chiusi.

#apro il file
file = File.open('miofile.txt', 'r')

#leggo tutto il contenuto e lo metto nella variabile contenuto.
# contenuto avrà dentro tutto il testo come stringa (se il file è di testo)
contenuto = file.read

#ho finito di leggerlo e lo chiudo: mai lasciare aperti i file.
file.close

Qui sopra ci sono le fasi. Una cosa molto importante è: chiudere i file il prima possibile dopo averci lavorato.

Chiudere i file è importante per vari motivi, due di questi:

  • è pericoloso tenere aperto un file, si potrebbe distruggere o alterare
  • i file sono una risorsa onerosa per il sistema operativo.

Il sistema operativo, può tenere aperti solo un certo numero di file alla volta e per tutto il sistema. Questo è un limite che può essere impostato, ma sappiate che comunque ha un impatto sulla velocità con cui viene poi gestito il computer: l'hardware del computer. Ogni volta che aprite un file, si occuperà una parte di memoria da un oggetto del sistema operativo chiamato descrittore. Questo succede perché il vostro programma non gestisce direttamente il file, ma parla col sistema operativo. Quando aprite un file chiedete al sistema operativo di aprirvelo e quando ci scrivete o leggete chiedete al sistema di leggerlo o scriverlo, poi dite al sistema di chiuderlo.

Lui vi risponderà: - Era ora! -.

#apro il file
file = File.open('miofile.txt', 'r')

#leggo tutto il contenuto e lo metto nella variabile contenuto.
# contenuto sarà un Array di stringhe, un elemento per riga (se il file è di testo)
contenuto = file.readlines

#ho finito di leggerlo e lo chiudo: mai lasciare aperti i file.
file.close

Adesso ho letto il contenuto con il metodo readlines che mi restituisce un Array di String. Come vedete ci sono vari modi di leggere il contenuto di un file.

Questi sono modi adatti per file di piccole dimensioni, altrimenti si dovranno adottare diverse strategie sempre per non caricare troppo il sistema operativo.

I file, per esempio, si potrebbero leggere a pezzi più o meno grandi…

Il nostro file lo abbiamo letto, ma per scriverlo?

#apro il file
file = File.open('miofile.txt', 'w')

#scrivo dentro il file (se il file è di testo)
file.write('Ciao Mondo')

#ho finito di scrivere e lo chiudo: mai lasciare aperti i file.
file.close

Ho scritto la stringa Ciao Mondo nel file. Adesso sul disco avremo un file dal nome miofile.txt con dentro la frase: Ciao Mondo.

Visto che aprire e chiudere i file è così importante, Ruby ci mette a disposizione una versione del metodo open molto interessante:

contenuto = ''
#apro il file
File.open('miofile.txt', 'r') do |file|

#leggo tutto il contenuto e lo metto nella variabile contenuto.
# contenuto avrà dentro tutto il testo come stringa (se il file è di testo)
contenuto = file.read

end

Come si può vedere, al metodo è associata una closure. Questa versione sostanzialmente si prende cura di chiudere il file all' uscita del blocco della closure.

Insomma se scriviamo così non ci dobbiamo preoccupare di chiudere il file perché sarà automatico. Non è un vantaggio?

La classe File, come vi ho già detto, ha molti metodi per la gestione dei file. Alcuni interessanti sono questi:

  • File.exist?('miofile.txt') controlla se il file esiste.
  • File.file?('miofile.txt') controlla se un file è un file.
  • File.directory?('miadirectory') controlla se un file è una cartella (directory).
  • File.ftype('miofile.txt') controlla che tipo di file è, per esempio se è un file o una directory.
  • File.size('miofile.txt') controlla quando è grande, quanto spazio occupa sul disco.
  • File.join('cartella1', 'cartella2', 'miofile.txt') costruisce un percorso di file concatenando due o più stringhe.
  • File.basename('/cartella1/cartella2/miofile.txt') ritornerà solo il nome del file miofile.txt.
  • File.dirname('/cartella1/cartella2/miofile.txt') ritornerà solo il percorso delle directory senza il nome del file.
  • File.extname('miofile.txt') ritornerà l'estensione del nome del file, cioè il testo dopo il punto ma col punto compreso (.txt).

Ce ne sono altri di metodi e ci sono, inoltre, anche altre classi per la gestione dei file. I file non si creano, leggono o scrivono soltanto; ma si rinominano, spostano, cancellano e così via.

Quello che abbiamo visto qui serve per i file di testo, cioè quelli che contengono caratteri e che quando si leggono vi si accede come fossero delle stringhe. I file oltre che di testo possono essere anche binari e quindi codificati direttamente in byte. Vi si accede praticamente nello stesso modo, ma bisogna usare con i metodi che vi ho detto altri parametri. Non è particolarmente semplice, quindi per adesso va bene così.

3.5.5 I dizionari

Conoscerete il dizionario, quel librone grosso con tante parole in fila… I significati delle parole. Quello che va da abaco a zuzzurellone (per la verità, se controllate comincia con la a, però è così che corre voce).

Sappiate che Ruby ha i dizionari, la classe si chiama Hash (il motivo c'è ma è complicato). In altri linguaggi di programmazione si chiamano Map (mappa) ma il concetto è lo stesso.

Un dizionario o mappa o Hash è un tipo di dato estremamente utile.

dizionario = { 'nome' => 'massimo', 'cognome' => 'ghisalberti'}

Questo è come si dichiara velocemente. La variabile dizionario contiene un hash con due chiavi: nome e cognome. Se volessi accedere al valore indicato dalla chiave nome:

il_mio_nome = dizionario['nome']

La variabile il_mio_nome a questo punto conterrà massimo.

irb(main):001:0> dizionario = { 'nome' => 'massimo', 'cognome' => 'ghisalberti'}
=> {"nome"=>"massimo", "cognome"=>"ghisalberti"}
irb(main):002:0> dizionario['nome']
=> "massimo"
irb(main):003:0>

Vi assicuro che degli Hash non potrete farne a meno, sono utilissimi. In qualche maniera sono simili agli Array, nel senso che hanno metodi simili. Anche gli Hash sono collezioni:

dizionario = { 'nome' => 'massimo', 'cognome' => 'ghisalberti'}
dizionario.each do |chiave,valore|
  puts "chiave: " + chiave
  puts "valore: " + valore
end

Salvate come dizionario.rb ed eseguite:

➜  ruby dizionario.rb 
chiave: nome
valore: massimo
chiave: cognome
valore: ghisalberti
➜ 

come vedete, potete accedere in questo modo alle coppie chiave -> valore. I dizionari di Ruby sono estremamente flessibili, la chiave può essere di qualunque tipo supportato da Ruby e così i valori. Potete mischiare le cose…

dizionario = { 1 => 'massimo', 'cognome' => 'ghisalberti'}
dizionario.each do |chiave,valore|
  puts chiave
  puts valore
end

Eseguendolo, produrrà questo:

➜  ruby dizionario.rb 
chiave: 1
valore: massimo
chiave: cognome
valore: ghisalberti
➜ 

Adesso una chiave è un Fixnum, un numero, mentre l'altra una stringa.

Adesso mi direte: - Perché i parli adesso degli Hash se sono così utili? -

La risposta è che, perché vi ho parlato di come si salva un file. Questa è la risposta corta. Quella lunga invece è che un hash (ma anche un Array) può essere facilmente serializzato. Che vuol dire?

require 'yaml'

rubrica = [

  {:nome => 'Massimo', :cognome => 'Ghisalberti', :telefono => '1234567890'},
  {:nome => 'Mario', :cognome => 'Rossi', :telefono => '1234567890'}

]

puts "La rubrica come array"
p rubrica
puts

puts "la rubrica serializzata come YAML"
p rubrica.to_yaml
puts

puts "Salvo la rubrica su un file"
puts
File.open('rubrica.yml', 'w') do |file|
  file.write(rubrica.to_yaml)
end


puts "Leggo la rubrica su un file"
puts
dati = []
File.open('rubrica.yml', 'r') do |file|
  yaml = YAML.load(file.read)
  dati = yaml.to_a
end

puts "La rubrica ricaricata dal file"
p dati

Salvate come dizionario2.rb ed eseguite.

➜  ruby dizionario2.rb
La rubrica come array
[{:nome=>"Massimo", :cognome=>"Ghisalberti", :telefono=>"1234567890"}, {:nome=>"Mario", :cognome=>"Rossi", :telefono=>"1234567890"}]

la rubrica serializzata come YAML
"---\n- :nome: Massimo\n  :cognome: Ghisalberti\n  :telefono: '1234567890'\n- :nome: Mario\n  :cognome: Rossi\n  :telefono: '1234567890'\n"

Salvo la rubrica su un file

Leggo la rubrica su un file

La rubrica ricaricata dal file
[{:nome=>"Massimo", :cognome=>"Ghisalberti", :telefono=>"1234567890"}, {:nome=>"Mario", :cognome=>"Rossi", :telefono=>"1234567890"}]

È un piccolo programma che salva un array di hash e cioè una collezione di dizionari su un file e la ricarica. Ho utilizzato un formato di dati molto comune nel mondo Ruby, lo YAML (http://yaml.org/). È un formato di struttura dati abbastanza semplice e versatile e soprattutto testuale. Se aprite il file rubrica.yml vedrete che è così:

---
- :nome: Massimo
  :cognome: Ghisalberti
  :telefono: '1234567890'
- :nome: Mario
  :cognome: Rossi
  :telefono: '1234567890'

Se volete potete aggiungere direttamente qui:

---
- :nome: Massimo
  :cognome: Ghisalberti
  :telefono: '1234567890'
- :nome: Mario
  :cognome: Rossi
  :telefono: '1234567890'
- :nome: Pico
  :cognome: De paperis
  :telefono: '0234567890'

Quando lo rileggerete nel modo indicato avrete un dizionario in più nel vostro array. Utilizzando il metodo to_yaml ho convertito, in questo caso l'array ma anche tutti i tipi di dati in esso contenuti, in una struttura dati Yaml che ho salvato sul disco. Dopo ho ricaricato i dati dal file aperto come struttura dati Yaml attraverso YAML.load e convertito di nuovo in dati Ruby con to_a (to array). Sembra contorto ed un po' lo è, ma questa "cosa" vi permette di avere facilmente il modo di poter salvare velocemente dati sul disco e di recuperarli in seguito.

Pensate anche che lo Yaml non lo gestisce solo Ruby, ma si può aprire e leggere da molti altri linguaggi di programmazione.

Per amor di cronaca esiste anche un altro sistema di strutture dati testuali che oggi va per la maggiore ed è il JSON (http://json.org/). Ruby ha un serializzatore e deserializzatore anche per il JSON.

require 'json'

rubrica = [

  {:nome => 'Massimo', :cognome => 'Ghisalberti', :telefono => '1234567890'},
  {:nome => 'Mario', :cognome => 'Rossi', :telefono => '1234567890'}

]

puts "La rubrica come array"
p rubrica
puts

puts "la rubrica serializzata come YAML"
p rubrica.to_json
puts

puts "Salvo la rubrica su un file"
puts
File.open('database.json', 'w') do |file|
  file.write(rubrica.to_json)
end


puts "Leggo la rubrica su un file"
puts
dati = []
File.open('database.json', 'r') do |file|
  json = JSON.load(file.read)
  dati = json.to_a
end

puts "La rubrica ricaricata dal file"
p dati

Come vedete i metodi sono molto simili.

Sia per il JSON che lo Yaml dovete, prima di usarli, richiederli con la require.

Come ultima cosa sugli Hash voglio farvi notare una cosa: ho usato per le chiavi il tipo Symbol. Il simbolo è un tipo particolare simile ad una stringa, ma non è mutabile, non si possono cioè manipolare i caratteri che lo compongono. Ha una sua importanza in molti casi, ma soprattutto come chiavi dentro i dizionari dove si suppone che la chiave non cambi mai, ma cambi solo il valore. Ruby fa della magia sua interna con i simboli ma spiegarla va oltre lo scopo di questo piccolo corso.

3.5.6 Le finestre che si aprono o si chiudono se fa freddo.

Apriamo le finestre, oggi si arieggia.

3.5.6.1 Divagazioni sul tema

\hfill\break

Una delle parti più complicate e noiose del programmare sono le interfacce grafiche. Tutti voi avete dei sistemi operativi grafici, più o meno basati sul paradigma della finestra o sul padadigma della vista.

Sappiate che contrariamente a quello che sapete o avete sentito più spesso dire, Apple o Microsoft in questo non si sono inventati niente. Insomma Jobs e Gates hanno solo rubacchiato qua e là.

Orrore! Qualcuno si sentirà male, qualcuno mi vorrà picchiare per aver insultato i suoi idoli (specialmente quelli a cui piacciono le mele).

Ragazzi, tenetevi forte…

Tutto quello che conoscete è stato inventato dalla Xerox in certi laboratori, il PARC, che aveva negli anni '70. A Palo Alto in California, i ricercatori geniali della Xerox hanno inventato l'informatica moderna.

  • Il mouse (questo lo conoscete tutti)
  • la stampa laser (anche questa dovrebbe essere chiara)
  • l'ethernet (ogni computer oggi ha una scheda ethernet per collegarsi a Internet)
  • la grafica bitmap (le fotografie le fate?)
  • le interfacce grafiche (Le finestre di Microsoft Windowstm o di Apple MacOSXtm?)
  • gli editor WYSIWYG, What You See Is What You Get (i word processor, avete presente Microsoft Wordtm?)
  • Interpress (poi evoluto da Adobe nel postscript e finito nel PDF)
  • la programmazione orientata agli oggetti (con Smalltalk)
  • l'architettura MVC, Model View Controller (che oggi è usatissima)
  • gli LCD (gli schermi a cristalli liquidi)
  • i dischi ottici di memorizzazione (Philips poi fece il CD su queste ricerche nell'80)
  • ubiquitous computing (il calcolo distribuito)
  • la programmazione ad aspetti (Aspect Oriented Programming)
  • IPv6 (il nuovo protocollo per la comunicazione tra computer che ancora non abbiamo, Internet per adesso è ancora basato sull IPv5)

Tutto questo è stato studiato e, o, inventato tra la fine degli anni '60 ed i primi anni '80.

Alan Key, uno di questi pionieri ed inventore Smalltalk (implementato da Dan Ingalls), propose nel 1972 il Dynabook. Dynabook sarebbe dovuto essere un computer portatile a batteria (simile ad un tablet odierno, quindi Apple non ha inventato il tablet), con una batteria virtualmente eterna, collegato in rete wireless, con un sistema operativo grafico.

Durante questa ricerca, Key, inventò Smalltalk ed i sistemi operativi grafici. Stimò allora (1972) per la costruzione del Dynabook un costo di 6000 dollari.

Alan Key è fortemente impegnato nell'insegnamento della programmazione ai bambini e nella divulgazione dell'informatica a tutti i livelli, per esempio nel progetto: One Laptop Per Child (http://en.wikipedia.org/wiki/One_Laptop_per_Child) che si prefiggeva la costruzione e distribuzione a basso costo di computer adatti all'insegnamento e per zone disagiate (XO-1 è uscito intorno al 2008).

3.5.6.2 Facciamole queste finestre, che ci siamo annoiati!

\hfill\break

Dopo questa divagazione sul tema lunga e noiosa veniamo al dunque.

Programmare l'interfaccia grafica di un programma è un lavoro lungo e spesso noioso, non è difficile ma ripetitivo e dispersivo. Se prima lanciare a linea di comando vi era sembrato brutto e noioso dopo questo lo amerete!

3.5.6.2.1 il problema

\hfill\break

Esistono molte librerie per la definizione delle interfacce grafiche, il sistema operativo grafico ne ha una sua che è spesso abbastanza complicata. Microsoft Windowstm ha la sua, il MacOSX la sua, i vari desktop Linux (KDE, Gnome per citarne solo due) la loro, Android la sua, IOS la sua. È un vero caos (ricordate che casino è una parolaccia e non si può dire).

Pensate poi al problema di un programma fatto per funzionare su tutti questi sistemi. Non è più un caos, è un vero incubo… Altro che i mostri sotto il letto o dentro l'armadio.

Per cercare di facilitarsi la vita, molti programmatori hanno scritto delle librerie che però hanno le loro regole. Insomma caos aggiunto al caos.

La scelta di una buona libreria è importante per la durata e la mantenibilità (un software va mantenuto, curato e ci vanno aggiunte funzionalità o tolte) di un programma per computer.

Ci sono linguaggi di programmazione migliori di altri in questo, alcuni sono stati appositamente progettati per la definizione delle interfacce e quindi alcune cose sono facilitate.

Sfortunatamente Ruby non è uno di questi, ma ha una cosa molto importante: è Ruby. Poi, forse, questa affermazione sarà più chiara.

Per descrivere una interfaccia grafica abbiamo bisogno di usare una libreria apposta.

3.5.6.2.2 Le scarpette rosse

\hfill\break

In questo mini corso vedremo come usare Shoes (http://shoesrb.com/).

Dovete scaricare una distribuzione di Shoes per il vostro sistema http://shoesrb.com/downloads/). Una volta installata avrete un eseguibile che si chiama shoes con cui potete lanciare i vostri programmi. Se volte potete lanciare anche i vecchi programmi che avete fatto fino ad adesso, Shoes è una libreria per Ruby che fornisce un comando per convenienza. Ruby c'è ancora e funziona.

shoes tabellina.rb

L'unica cosa è che il programma non termina da solo ma dovete fermarlo con la combinazione di tasti: CRTL+c (CTRL è il tasto Control, cercatelo sulla tastiera e premete prima quello e tenendolo premuto battete il tasto c, poi lasciateli tutti e due).

Vi ho fatto scaricare una distribuzione di Shoes completa perché non è facile da installare dentro Ruby specialmente se usate Microsoft Windowstm.

Cominciamo con una cosa semplicissima:

Shoes.app { 
  button "Ciao, premi..." 
}

Salvatelo come scarpe.rb ed eseguitelo con: shoes scarpe.rb.

Alleluja! Il vostro primo programma grafico! Con Anche un bottone!

La finestra la potete spostare, ingrandire, rimpicciolire… Insomma ha un po' tutto. Tutto quello che è dentro Shoes.app è la nostra applicazione Shoes.

Aggiungiamo un altro bottone?

Shoes.app { 
  button "Ciao, premi..."
  button "Io sono il secondo bottone, premi..." 
}

Salvatelo come scarpe2.rb ed eseguitelo con: shoes scarpe2.rb.

Vogliamo disegnare un cerchio? Via!

Shoes.app { 
  button "Ciao, premi..."
  button "Io sono il secondo bottone, premi..." 

  oval(left: 20, top: 40, radius: 50)

}

Salvatelo come scarpe3.rb ed eseguitelo con: shoes scarpe3.rb.

Lo vogliamo rosso?

Shoes.app { 
  button "Ciao, premi..."
  button "Io sono il secondo bottone, premi..." 

  fill(red)
  oval(left: 20, top: 40, radius: 50)

  fill(green)
  rect(left: 20, top: 100, width: 50)

}

Salvatelo come scarpe4.rb ed eseguitelo con: shoes scarpe4.rb.

Ho fatto anche un quadrato verde. Shoes ha numerosi oggetti grafici già disegnati:

  • oval per disegnare ovali, quindi anche cerchi
  • rect per i rettangoli e quadrati
  • star per fare delle stelle
  • image per caricare immagini, anche via internet
  • arrow per fare delle frecce
  • video per guardare dei video

Oltre questi metodi per avere cose già fatte, possiamo sempre disegnarle noi con stroke (tratto) e fill (riempi). Insomma, con penna e colore potete disegnare.

Shoes.app do
  @stella = star(points: 5)
  motion do |sinistra, alto|
    @stella.move sinistra, alto
  end
end

Salvatelo come scarpe5.rb ed eseguitelo con: shoes scarpe5.rb. Muovete il mouse. la stella vi viene dietro.

Shoes.app do
  fill(yellow)
  @cerchio = oval(left: 40, top: 40, radius: 40)
  animate do |i|
    @cerchio.top += (-20..20).rand
    @cerchio.left += (-20..20).rand
  end
end

Salvatelo come scarpe6.rb ed eseguitelo con: shoes scarpe6.rb. Abbiamo animato con il metodo animate ed avremo un cerchio giallo che ballonzola in giro in maniera casuale.

La forma: (-20..20) è un Range, serve per esprimere un intervallo di numeri. Il metodo rand ne prende uno a caso dentro questo Range.

L'espressione:

variabile = 1

#questa forma è equivalente a quella dopo
variabile += 1

#questa forma è equivalente a quella prima
variabile = variabile + 1

utilizza un operatore che chiameremo di incremento +=. Ruby ha una serie di operatori misti come questo, per esempio utile è:

variabile ||= 1

che assegnerà il valore 1 alla variabile solo se non contiene già un valore. Utile per inizializzare le variabili.

Ritorniamo al nostro bottone di prima:

Shoes.app { 
  button("premi...") {
    alert("Ciao Mondo!")
  }
}

Salvatelo come scarpe7.rb ed eseguitelo con: shoes scarpe7.rb.

Premete il bottone… Avete aperto una finestra di dialogo, una di quelle che se non le chiudete non potete fare più niente (in gergo difficile si dice finestra modale).

Shoes.app { 
  button("premi...") {
    alert("Ciao Mondo1!")
  }
  button("premi...") {
    alert("Ciao Mondo2!")
  }
}

Salvatelo come scarpe8.rb ed eseguitelo con: shoes scarpe8.rb.

Siore e Siori abbiamo due bottoni che funzionano!

Vediamo un campo di input, salvate come scarpe8.rb e lanciate:

Shoes.app do

  background "#00ffff"
  border("#ff0000", strokewidth: 6)

  stack(margin: 12) do
    title("Giochino")
    para("Inserisci il tuo nome:")
    flow do
      @input_field = edit_line
      button("OK") do
         alert("Ciao " + @input_field.text)
      end
    end
  end
end

Se lo eseguite, scrivete il vostro nome e premete il bottone; una finestra modale vi dirà: - Ciao … -.

Qui possiamo notare diversi elementi. il metodo stack che impila gli elementi che contiene, a cui possiamo fornire un margine e cioè di quanto è rientrato rispetto all'elemento che lo contiene (in questo caso la finestra principale). Il metodo flow invece permette di allineare, far fluire, i due elementi edit_line e button mantenendoli uno accanto all'altro. Il contenuto del campo di input che ho chiamato edit_field è recuperato con il metodo text del campo di input. Il metodo para sta per paragrafo (paragraph) e serve per scrivere una linea di testo, mentre title mette del testo in un carattere più grande.

Per colorare la finestra abbiamo usato background e border, il primo riempe di colore lo sfondo e il secondo costruisce un bordo intorno (la proprietà strokewidth (larghezza del tratto) è lo spessore del bordo.

Ci sono altri metodi per i testi che possono essere usati insieme, salvate come scarpe10.rb:

Shoes.app {
  stack(margin: 6) {
    title("Formattazione del testo")
    para(strong("Domanda"), " Quanto sei alto?")
    para(em(strong("Risposta"), " Sono alto un metro e mezzo."))
  }
}

strong metterà il testo in grassetto mentre em lo metterà in corsivo.

Shoes.app(title: "Il bottone") {

  stack {
    @bottone = button( "Premi") 
    @note = para(em("Non hai ancora premuto"))
  }

  num = 0

  @bottone.click {
    num += 1
    @note.text = "Hai premuto " + num.to_s + " volte"
  }

}

Se salvate come scarpe11.rb ed eseguite, vedrete che vi comunicherà il numero delle volte che avete premuto il bottone.

Adesso riprendiamo del codice che avevamo già scritto, le nostre tabelline:

Shoes.app(title: "la tabellina", width: 340, height: 280) {

def tabellina
  numeri = [1,2,3,4,5,6,7,8,9,10]
  (numeri.map { |numero|
          (numeri.map { |moltiplicatore|
             valore = numero * moltiplicatore
            (valore < 10 ? '0' : '') + valore.to_s + ' | '
          }).join
        }).join("\n")
 end

  stack(margin: 10) {
    @tabellina = para(strong(tabellina))
    @tabellina.style(fill: red, stroke: white)
  }

}

Scrivete, salvate come scarpe12.rb ed eseguite. Qui ho usato anche il metodo style per colorare il paragrafo: di rosso lo sfondo e bianco il testo.

Adesso rifacciamola ancora, un po' meglio (forse).

Shoes.app(title: "la tabellina", width: 412, height: 412, resizable: true) {

  def tabellina
    numeri = [1,2,3,4,5,6,7,8,9,10]
    numeri.map { |numero|
      numeri.map { |moltiplicatore|
        numero * moltiplicatore
      }
    }
  end

  width = 40
  height = 40
  top = 0

  tabellina.each_index { |numero_riga|
    stack(margin_left: 2) { 
      flow {
        left = 0        
        tabellina[numero_riga].each_index { |numero_colonna|
          valore =  tabellina[numero_riga][numero_colonna]
          pad = if valore < 10
                  '  '
                elsif valore >= 10 && valore < 100
                  ' '
                else
                  ''
                end          
          button = button(valore.to_s + ' ')
          button.style(top: top, left: left, width: width, height: height)
          button.instance_variable_set(:@coord, [numero_riga + 1, numero_colonna + 1])
          button.click { |btn|
            coord = btn.instance_variable_get(:@coord)      
            alert("Si ottiene moltiplicando #{ coord.join(' x ')}, oppure #{ coord.reverse.join(' x ')}")
          }

          left += width + 1
        }
      }
      top += height + 1
    }
  }
}

Salvate come scarpe13.rb ed eseguite.

Qui ho usato anche delle caratteristiche particolari di Ruby, la sua dinamicità. Ho creato una variabile di istanza mentre il programma funziona. Può sembrare normale, ma vi assicuro che non lo è nei linguaggi di programmazione. Ruby, per questo (ma ce ne sono anche altri) è considerato un linguaggio dinamico. Con il metodo instance_variable_set ho impostato la variabile ed il suo valore mentre con instance_variable_get l'ho letto in seguito.

Si possono fare molte cose con le caratteristiche dinamiche, come per esempio creare al volo metodi, o eliminarli da una istanza.

Guardate come una espressione if..else..end restituisca un valore e per questo la chiamo espressione. Guardate come si possono fare test multipli con la sintassi elsif che sta per else if.

Con questo esempio abbiamo concluso questa superficiale panoramica su Shoes e vi rimando al sito ufficiale per imparare ulteriormente.

Shoes è decisamente lenta, ve ne sarete accorti, ma era nata per una scopo bene preciso che non è ovviamente quello di creare applicazioni complesse. È interessante per imparare alcune delle regole base della programmazione ad eventi per le interfacce grafiche; le cose più serie sono possibili anche in Ruby, ma bisogna andare su librerie notevolmente più complesse.

3.6 Conclusioni

Abbiamo solo grattato la superficie e spero di avervi almeno fatto venire la curiosità su cosa voglia dire programmare. Ora qui, abbiamo terminato.

Vi rimando alla documentazione ufficiale dove troverete molte cose interessanti: http://www.ruby-doc.org/

Ruby è un linguaggio potente, adatto a molteplici usi. Se vogliamo trovare un difetto è che non è veloce, anche se nelle varie versioni lo è sempre di più.

Ruby ha caratteristiche che non fanno rimpiangere quello, perché rende programmare divertente e produttivi fin da subito il che non è niente male.

Potete fare della Metaprogrammazione per esempio e fare cose davvero magiche. Potete creare metodi al volo mentre il programma sta funzionando, estendere classi esistenti senza derivarle. Modificare comportamenti prefissati.

Il limite è davvero solo la fantasia.

Vorrei ringraziare Yukihiro Matsumoto, per averlo pensato ed inizialmente creato ed aver aperto le porte ad una selva di programmatori contenti e rilassati.

Lo dico molto spesso, Ruby è magico e divertente.

Data: 2014-10-21

Autore: Massimo Maria Ghisalberti - pragmas.org

Created: 2017-11-07 mar 15:00

Validate