Ruby language
   

Dati - stringhe                

Strumento potente e di enorme utilità nel campo della programmazione le stringhe di testo sono egregiamente gestite nell'ambito di Ruby. Inziamo subito dicendo che, diversamente da quanto avviene in alcuni linguaggi, in Ruby le stringhe sono dinamiche, mutabili e, operativamente parlando, molto flessibili. Va detto subito però che il fatto che le stringhe siano oggetti, come ogni altra entità in Ruby, così duttili  non comporta che si possa usare lo stesso oggetto per rappresentare la stessa stringa. Stringhe identiche corrispondono ad oggetti comunque diversi. Una stringa, in questo linguaggio è la stessa "cosa" che incontrate negli altri ovvero una sequenza di caratteri racchiusa in una coppia di apici. A partire dalla versione 1.9 è stato inserito pieno supporto built-in alla codifica Unicode e multi-byte. Gli esempi base sono i soliti:

"ciao"
"1234"
"Forza Juve"

ecc... niente di particolare, insomma. Ruby tuttavia accetta le stringhe anche delimitate all'interno di una coppia di singoli apici:

'ciao'
'1234'
'Forza Juve'

Qual è la differenza? Come vedremo le stringhe racchiuse tra doppi apici sono oggetto di una qualche possibile "interpretazione" mentre quelle dentro i singoli apici sono stringhe "raw" così come scritte così sono presentate. La creazione di una stringa avviene naturalmente tramite la sua attribuzione ad una variabile via assegnamento:

s1 = "stringa"

assegna la stringa tra doppi apici alla variabile s1. Ogni stringa creata è una oggetto istanza della classe stringa il che garantisce l'accesso a moltissimi metodi per la sua completa manipolazione. Non li vedremo tutti, tra l'altro si trovano perfettamente documentati sul sito ufficiale, tuttavia alcuni sono dei must per lavorare fin dall'inizio. E' comunque possibile utsare un metodo molto object oriented:

String.new che senza argomento crea una stringa vuota, diversamente crea una stringa col contenuto dato. Non molto comune ma è un'altra possibilità.

irb(main):098:0> a = String.new("ciao")
=> "ciao"
irb(main):099:0> puts a
ciao

Le stringhe più semplici, proprio in quanto meno sofisticate sono ovviamente quelle delimitate da un singolo apice. Come detto non debbono essere interpretate e le uniche particolarità possono venire dall'uso al loro interno del carattere backslash \ che peraltro, come vedremo, non significa nulla nella pratica quando è seguito dalla maggior parte dei caratteri disponibili. Vediamo qualche esempio:

i
rb(main):004:0> s1 = 'po\\a'
irb(main):005:0> puts(s1)
po\a


Qui, essendo seguito da una lettera, il nostro backslash non ha effetto, che ne mettiate uno o due non sposta nulla. Lo stesso vale per la maggior parte dei caratteri che possiamo porre in coda al nostro \. Cale tuttavia la pena annotare che due backslash uno in coda alla'altro fanno un backslash solo. E 3 in fila? Provate.

irb(main):006:0> s2 = 'l\'altro'
irb(main):008:0> puts(s2)
l'altro


In quest'altro caso abbiamo potuto rappresentare l'apostrofo, se avessimo messo un apice senza la \ la stringa sarebbe stata considerata chiusa anticipatamente rispetto alle nostre intenzioni e l'interprete Ruby non avrebbe gradito il terzo apice a quel punto divenuto "vagante",

irb(main):009:0> s1 = 'aaa
irb(main):010:0' zzzz'
=> "aaa\nzzzz"
irb(main):011:0> puts(s1)
aaa
zzzz

Qui invece abbiamo l'esempio di una stringa spezzata su più righe, come l'output, in rosso, ci fa chiaramente capire. il backslash è necessario per impedire che l'interprete consideri il carattere di newline come terminatore della stringa. Naturalmente, con questa tecnica, è possibile presentare stringhe spezzate su 3, 4 o quante righe vogliamo.

Un altro uso del backslash in cingiunzione con le stringhe single quoted si ha quanto si vuole spezzare una riga di testo molto lunga che il parser poi ricostruirà correttamente:

'questa è una stringa particolarmente lunga'\
'spezzata su più righe tramite il carattere backslash'\
'allo scopo di migliorarne la visualizzazione e la comprensione'\
'a runtime tuttavia sarà scritta in una sola riga'

Tuttavia le vere protagoniste nel campo delle stringhe in Ruby sono quelle comprese all'interno di una coppia di doppi apici. Sono flessibili e potenti e nella pratica incontrerete molto più spesso queste rispetto alle altre. Un primo vantaggio che ci viene offerto da questa tipologia di stringhe è che possono gestire un discreto numero di formattazioni tramite le sequenze di escape introdotte come sempre dal fedele backslash. Vediamo l'elenco delle più comuni sequenze di escape, eventualmente non è difficile trovare su Internet le tabelle complete:

\" – doppio apice
\\ – singolo backslash
\a – beep!
\b – backspace (indietro di uno spazio)
\r – ritorno del carrello (torna a inizio riga)
\n – newline (a capo)
\s – spazio
\t – tabulazione

In realtà l'elenco è molto più corposo e in effetti le sequenze di escape costituendo argomento di una certa complessità potrebbero anche dare origine a qualche problema interpretativo. Pertanto ci fermiamo ad un livello di base, caso mai ne parleremo in maniera più particolareggiata altrove. Vediamo ora un piccolo esempio:

  Esempio 3.1
1
2
3
puts("\a\a\a")
puts("riga sopra\nriga sotto")
puts("a\tb")

importante ed interessante è anche la sequenza \u che permette, dalla versione 1.9 di inserire sequenze Unicode a piacere in modo molto semplice:

irb(main):002:0> puts "\u0117"
e
irb(main):082:0> puts("du\u0117")
due

Ancora più interessante è la possibilità, gia sfiorata nel capitolo precedente, di poter inserire qualsiasi espressione Ruby all'interno di una stringa delimitata dal doppio apice; questo è possibile attraverso una sintassi molto semplice:

#{ espressione }

esempio:

irb(main):004:0> puts "somma #{2 + 3}"
somma 5
=> nil
irb(main):005:0> puts "somma #{(2 + 3) * 5 + Math::PI}"
somma 28.141592653589793

Ecco quindi spiegato il meccanismo al quale abbiamo accennato nel capitolo "Hello, World". Ne faremo uso spesso e vedremo quanto sia pratico.

Ruby supporta anche un meccanismo alternativo di delimitazione delle stringhe e questo a beneficio della leggibilità. In pratica viene incontro alla necessità di scrivere parole ricche di accenti e apici vari che risulterebbero un po' criptiche usando le regole normali. Per fare questo riccorriamo a ue delimitatori particolari:

  • %q - che introduce una stinga che segue le regole delle stringhe delimitate con una coppia di singoli apici

  • %Q o semplicemente % - che introduce una stringhe che segue le regole di quelle delimitate da una coppia di doppi apici

il testo deve poi essere messo all'interno di una coppia di delimitatori, nel caso nostro primo esempio è stato messo tra parentesi tonde. Il primo carattere che segue la q o la Q è il delimitatore e la stringa viene presa in considerazione fino al reperimento del secondo delimitatore (ovviamente uguale al primo o suo logico complemento ovvero, in questo senso, il carattere - come vedremo, ha come chiusura un altro -, lo stesso dicasi per il + o il ! ecc... mentre i simboli di parentesi aperta hanno il loro corrispondente simbolo di chiusura come secondo delimitatore e lo stesso dicasi per il simbolo < che viene chiuso tramite >), che non deve essere preceduto dal backslash. Valgono le stesse regole di escape viste per le tipologie di stringhe "normali" mentre è più facile la gestione degli apici:

irb(main):088:0> puts(%q(un'altra possibilita' offerta da Ruby!))
un'altra possibilita' offerta da Ruby!
=> nil
irb(main):089:0> puts(%Q(un'altra possibilita' offerta da Ruby! ' " ""))
un'altra possibilita' offerta da Ruby! ' " ""

Ecco un esempio con delimitatore alternativo, il "meno"

irb(main):090:0> puts (%Q-ciao\namico-)
ciao
amico

nb: questo dei delimitatori alternativi di una stringa è un lodevole tentativo che, a mio avviso, potrebbe anche indurrre qualche complicazione....

Altra particolarità che coinvolge i delimitatori sono i cosiddetti here documents. Il nome nasce dal fatto che si tratta di stringhe particolarmente lunghe, dei veri e propri documenti all'interno del codice, ecco perchè "here documents". Essi sono introdotto dal particolare delimitatore << o <<- al quale deve essere letteralmente appiccicato senza spazi di mezzi il delimitatore di chiusura; inoltre il testo del documento inizia alla riga successiva (anche se è permesso scrivere altro testo sulla riga in cui poniamo i simboli << o <<- seguiti dal delimitatore di chiusura; tale testo sarà preso in esame dopo il testo del documento). Esempio:

irb(main):091:0> doc = <<STOP
irb(main):092:0" eccoci all'inizio del documenmto
irb(main):093:0" che prosegue su varie righe
irb(main):094:0" con vari contenuti che il lettore
irb(main):095:0" trova all'interno del codice
irb(main):096:0" STOP
=> "eccoci all'inizio del documenmto\nche prosegue su varie righe\ncon vari cont
enuti che il lettore\ntrova all'interno del codice\n"
irb(main):097:0> puts doc
eccoci all'inizio del documenmto
che prosegue su varie righe
con vari contenuti che il lettore
trova all'interno del codice

il documento viene aperto con << e terminerà col delimitatore di chiusura STOP. al suo interno l'estensore non si deve preoccupare di inserire newline o di come gestire gli apici. In effetti in qualche caos può essere utile. Il delimitatore finale deve stare su di una riga da solo, senza commenti. Esso deve stare immediamente all'inizio della stessa se il here document veniva introdotto da << mentre può avere uno spazio davanti se l'introduzione era effettuata con <<-
Gli here documents ci aprono alcune proprietà espressive:

  Esempio 3.2
1
2
3
4
5
puts 'Inizio','-------',<<XX, '------'
"a"
"b"
"c"
XX

Presenta il seguente output:

Inizio
-------
"a"
"b"
"c"
------

Potete fare le vostre prove e curiosate un po' in giro perchè gli here documents sono piuttosto variopinti come impiego.

Le stringhe fisicamente sono composte da una sequenza di caratteri, come abbiamo detto. Ogni elemento in una stringa è caratterizzato da un indice. Questo indice ne riporta la posizione nella stringa a partire da sinistra. L'indice iniziale, comunemente ad altri linguaggi, è  0, l'ultimo è uguale al numero di elementi - 1. L'accesso al singolo elemento avviene attraverso l'operatore, anche questo comune a moltissimi liguaggi, []. Insomma in questo senso non ci sono novità.  L'accesso avviene tramite numeri positivi. E' possibile usare anche i numeri negativi come indici e vedremo qui di seguito come si comportano, intuitivamente vanno in senso opposto rispetto agli indici positivi. Vediamo l'esempio che ci ispirerà qualche considerazione ulteriore:

 
  Esempio 3.3
1
2
3
4
5
6
7
8
s1 = "abcdefg"
puts(s1[1])
puts(s1[0])
puts(s1[6])
puts(s1[-1])
puts(s1[-7])
puts(s1[10])
puts(s1[10].to_i)

che presenta il seguente output:

b
a
g
g
a

0

la stringa si presenta quindi così indicizzata:

a b c d e f g
0 1 2 3 4 5 6
-7 -6 -5 -4 -3 -2 -1

da notare che se cercate di prelevare un elemento al di fuori del range previsto per la stringa, questo vale sia usando gli indici normali che quelli negativi, Ruby impone il valore di default 0 (output della riga 8). Nessun errore a runtime. Bello? Boh..... forse preferisco l'errore....comunque sapete cosa cercare, quanto meno.

METODI E OPERAZIONI SULLE STRINGHE.

Come detto le stringhe sono ricchissime di metodi. Lungi dal volerli elencare tutti vediamo in azione quelli, a mio avviso, più comunemente utili. Da parte mia darò solo qualche esempio d'uso, la cosa migliore ovviamente è fare delle prove da soli magari mescolando i vari metodi.

Abbiamo già visto chop (esiste anche chop! e capirete tra breve qual è la grossa differenza) che taglia l'ultimo carattere di una stringa, o meglio espone un nuova stringa priva dell'ultimo carattere. Tuttavia in realtà nella pratica una delle prime cose che si chiede di una stringa è la sua lunghezza. Per risolvere questo problema arriva in nostro soccorso il metodo length. A questo potremmo attaccare la tipica sequenza () ma, come per quasi tutti i metodi in Ruby, tale pratica è del tutto opzionale in assenza di parametri e difatti non l'abbiamo usata nel piccolo esempio che segue e nemmeno negli altri.

irb(main):001:0> s1 = "abcdefg"
=> "abcdefg"
irb(main):002:0> puts(s1.length)
7

Un metodo alternativo e sostanzialmente identico è size.

Trovare un elemento in una stringa può essere fatto in tanti modi uno è quello di trovarne l'indice le stringhe, o le sottostringhe cercate, ciò che può essere fatto tramite il metodo count:

  Esempio 3.4
1
2
3
4
5
s1 = "abcdefffffffg"
puts(s1.count("f"))
puts(s1.count("cde"))
puts(s1.count("z"))
puts(s1.count('f'))

Questo programma è in grado di riportare quante occorrenze ci sono nella stringa delle sottostringhe, o anche del carattere singolo come alla riga 5. In questo caso ricevere il valore 0 è ovviamente sintomo della non esistenza di quanto cercato.

Un altro sistema di ricerca è dato metodo index. Esso restituisce la prima occorrenza della sottostringa cercata oppure nil in caso di insuccesso della ricerca.

irb(main):017:0> s1 = "aeiou"
=> "aeiou"
irb(main):018:0> puts s1.index("ae")
0
=> nil
irb(main):019:0> puts s1.index("ei")
1
=> nil
irb(main):020:0> puts s1.index("r")

Banali ma utili può risultare upcase che trasforma una stringa in un'altra che ha tutti i caratteri maiuscoli

irb(main):001:0> puts("aRbcEETTaa".upcase)
ARBCEETTAA

L'opposto di upcase è downcase, che ripresenta la stringa con soli caratteri minuscoli. E poi esiste anche swapcase che, indovinate un po', scambia le maiuscole con le minuscole e viceversa. Se volete essere sicuri di avere solo il primo carattere maiuscolo allora dovete usare capitalize.

Nota interessante ed importante: molti metodi in Ruby hanno una controparte che termina col punto esclamativo, ad esempio upcase!, downcase! swapcase! e capitalize!. Il funzionamento è identico a quelli diciamo originali che non ce l'hanno ma a differenza di questi, i metodi che terminano col ! causano effetti anche sulla stringa origine. Ossia non si tratta solo di esporre l'output modificato o di poterlo assegnare ad un'altra stringa. Se usate i metodi che terminano col punto esclamativo avrete delle conseguenze dirette anche sulla stringa da cui siete partiti. Questo è da ricordare e lo vedremo operativamente nell'esempio 3.5.

Se volete concatenare due stringhe esistono vari sistemi. Il più semplice è usare un overload dell'operatore +:

irb(main):012:0> str1 = "aaa"
=> "aaa"
irb(main):013:0> str2 = "bbb"
=> "bbb"
irb(main):014:0> puts(str1 + str2)
aaabbb

In questo caso in output abbiamo la "somma" delle due stringhe che peraltro potrebbe anche essere riversata in un'altra stringa:

irb(main):015:0> str1 = str1 + str2
=> "aaabbb"

Un modo alternativo è usare l'operatore << oppure il metodo concat che hanno la caratteristica di modificare la stringa posta alla loro sinistra:

irb(main):001:0> s1 = "aaa"
=> "aaa"
irb(main):002:0> s2 = "bbb"
=> "bbb"
irb(main):003:0> s1 << s2
=> "aaabbb"
irb(main):004:0> puts s1
aaabbb
=> nil
irb(main):005:0> s1.concat(s2)
=> "aaabbbbbb"
irb(main):006:0>

Oltre al + sulle stringhe potete usare anche il *, se vi serve:

irb(main):041:0> puts "ciao" * 3
ciaociaociao

Se una stringa non vi piace più potete azzerarla pur mantenendo in vita la variabile originaria usando il metodo clear:

irb(main):006:0> str1 = "abc"
=> "abc"
irb(main):007:0> str1.length
=> 3
irb(main):008:0> str1.clear
=> ""
irb(main):009:0> str1.length
=> 0

Se invece vi piace tanto che volete copiarla potete farlo col semplice segno =

irb(main):005:0> s1 = "abc"
=> "abc"
irb(main):006:0> s2 = s1
=> "abc"

In questo caso tenete presente che le due stringhe s1 ed s2 puntano alla stessa zona di memoria per cui una modifica su una ha effetto anche sull'altra, come è facile verificare. Se volete una copia indipendente dovete usare il meotodo generico dup:

s2 = s1.dup

La stessa criticità si presenterà parlando degli array

Se invece è solo una parte della stringa a non piacervi ecco correre in vostro aiuto il metodo delete: oppure la sua controparte più rischiosa delete! (si, la differenza sta nel punto esclamativo, come detto poc'anzi in Ruby vi sono molti metodi così costruiti per la gestione delle stringhe ed è parecchio importante). Abbiamo già detto in cosa consiste la differenza tra un metodo col punto esclamativo finale e quello senza e la cosa che risulta evidente eseguendo il seguente codice:

  Esempio 3.5
1
2
3
4
5
s1 = "aabbccdd"
puts(s1.delete("d"))
puts(s1)
puts(s1.delete!("d"))
puts(s1)

Il cui output è il seguente:

aabbcc
aabbccdd
aabbcc
aabbcc

ovvero: la riga due espone il risultato ma, come è evidente dalla 3, il tutto resta inalterato. La riga 4 fornisce un risultato identico alla 2 ma la riga 5 di rivela che anche la stringa originaria è stata privata di alcuni elementi, nello specifico la sottostringa "d". La cosa funzionava comunque allo stesso modo se anzichè come stringa la lettera da togliere fosse stata indicata come carattere, ovvero 'd'.

Volete capovolgere una stringa? Allora vi serve reverse (o il suo fratello più distruttivo reverse!)

Volete incrementare l'ultimo carattere? Ecco arrivare next o, più divertente next!

irb(main):066:0> a = "aaaa"
=> "aaaa"
irb(main):067:0> puts a.next!
aaab
=> nil
irb(main):068:0> puts a.next!
aaac
=> nil
irb(main):069:0> puts a.next!
aaad

Molti linguaggi propongono dei metodi per eliminare i blank intorno ad una stringa. Ruby non fa eccezione e ne ha predefiniti 3 pronto all'uso

1) lstrip elimina gli spazi a sinistra
2) rstrip elimina gli spazi a destra
3) strip li elimina da entrambi i lati.

Anche in questo caso esistono i corrispondenti col ! finale. Il funzionamento dei 3 metodi è illustrato qui di seguito:

irb(main):005:0> str1 = " aaa "
=> " aaa "
irb(main):007:0> puts "/" + str1.lstrip + "/"
/aaa /
=> nil
irb(main):008:0> puts "/" + str1.rstrip + "/"
/ aaa/
=> nil
irb(main):009:0> puts "/" + str1.strip + "/"
/aaa/

accenniamo al metodo split che suddivide una stringa in un array (vedremo nel prossimo paragrafo cosa sono) ciascun elemento del quale è una parte della stringa stessa. Le possibilità offerte da strip sono davvero tante, in questa sede accenniamo solo al caso più generale riservandoci un ulteriore futuro approfondimento di questo metodo.

irb(main):014:0> a2 = "uno due tre".split
=> ["uno", "due", "tre"]

Non dimentichiamoci poi i semplici metodi di conversione verso il mondo numerico:

to_c ovvero to complex, per convertire in numero complesso
to_f ovvero to float, per convertire in float
to_i ovvero to int, per convertire ad intero
to_r ovvero to rational, per convertire a numero razionale

Un altro aspetto interessante è quello che riguarda l'operatore []; questi è infatti molto più potente di quel che abbiamo visto finora. Esso si permette di operare su intere porzioni (al limite anche ail 100%) della stringa su cui lavoriamo. L'uso allargato di questo operatore è definito in questo modo:

[indice iniziale, numero elementi da esaminare]

Gli esempi sono semplici e ne potete inventare senza problemi:

  Esempio 3.6
1
2
3
4
5
6
s1 = "abcdef"
puts s1[0,4]
puts s1[3,5]
puts s1[0,10]
puts s1[-1,1]
puts s1[-6,7]

Col seguente output:

abcd
def
abcdef
f
abcdef

Le righe 4 e 6 dimostrano che non vi è particolare attenzione per il range degli indici... Ovviamente sono permesse operazioni di assegnazione:

s2 = s1[2,3]

Sono possibili, tanto per mostrare alcune delle possibilità di questo sistema, alcuni "giochini" curiosi, ne mostro alcuni poi dovrete integrare con i vostri esperimenti:

s1 = "abcdef"
s1[0,1] = ?R                     # s1 ora è Rbcdef
s1[s1.length,0]  = "ghi"         # s1 ora è Rbcdefghi
s1[3,0] = ?,                     # s1 ora è Rbc,defghi
s1[1,1] = ""                     # s1 ora è Rc,defghi

ecc... qualche commento: avrete notato il simbolo ? affiancato ad un carattere. Il suo scopo è proprio quello, quello di definire un carattere, una lettera in altre parole. Un po' come se fosse 'R' in C/C++. A questo proposito è possibile passare facilmente da valore numerico a singolo carattere e da quest'ultimo al suo corrispondente valore nella tabella ASCII:

puts 120.chr -> ci dà il carattere x
puts "x".ord -> ci restituisce il valore 120.

Per quamto banale siano queste due istruzioni è bene conoscerle, l'ultima in particolare è quella definitiva per le nuove versione del linguaggio al fine di risolvere la piccola questione del recupero del codice ASCII di un carattere (nota: prima della versione 1.9 l'istruzione ?a restituiva il codice ASCII del carattere a destra del punto interrogativo il che però spesso non era quello che i programmatori volevano)

Un ruolo particolare ce l'ha quello 0, di cui al terzo e quarto esempio e che ci permette di effettuare degli inserimenti. L'ultimo esempio ci mostra un esempio di cancellazione diversamente dal caso del delete, visto poc'anzi, possiamo andare a colpire in una posizione precisa. La cancellazione peraltro è possibile anche in altro modo, come vedremo tra breve.

In alternativa, se si vuole lavorare solo con gli indici possiamo riccorere ai range, definiti invece in questo modo:

[indice inizale .. indice finale]

La differenza rispetto al caso precedente è evidente; in precedenza avevamo un indice e un numero di elementi a partire dallo stesso mentre quando si parla di range la definizione degli stessi è dato da due indici che vengono inclusi. Vediamo ora un esempio:

  Esempio 3.7
1
2
3
4
5
s1 = "abcdef"
puts s1[1..4]
puts s1[2..3]
s2 = s1[0..4]
puts s2

Ovviamemte il tutto può essere facilmente parametrizzato, sia per quanto riguarda i range che per altri usi dell'operatore []:

s2 = s1[x .. x1+1] dove x è una variabile intera calcolata o predefinita.

Attraverso i range è possibile anche effettuare sostituzioni e cancellazioni in maniera molto semplice:

irb(main):024:0> str = "123456"
=> "123456"
irb(main):025:0> str[2..5] = "a"
=> "a"
irb(main):026:0> puts str
12a
=> nil
irb(main):027:0> str[1..2] = ""
=> ""
irb(main):029:0> puts str
1

Concludiamo questa breve panoramica con il metodo bytesize che riporta la lunghezza in bytes della stringa. Utile più che altro per verificare la presenza di caratteti multybyte, la cui gestione, come già accennato, è stata introdotta in Ruby dalla versione 1.9.

irb(main):050:0> puts "aaaa".bytesize
4

Infine, se ci tenete tanto ad una stringa e non volete che nel corso del programma essa venga mai modificata potete usare freeze:

irb(main):041:0> x = "ciao"
=> "ciao"
irb(main):042:0> x.freeze
=> "ciao"
irb(main):043:0> x << "ss"
RuntimeError: can't modify frozen String
from (irb):43
from d:/Ruby200-x64/bin/irb:12:in `<main>'

Come vedete x è "congelata" e nessuna la tocca più.

Un altro discorso interessante è anche quello relativo alle iterazioni compiute tramite le stringhe e sulle stesse ma di questo argomento parleremo a suo tempo. Altro importantissimo argomento è quello delle espressioni regolari, così ben gestite da questo linguaggio.

Come detto di materiale ce n'è a bizzeffe, non vi resta che andare sul sito ufficiale e verificare da voi stessi l'ampia gamma a disposizione e selezionare quello che vi può servire... non tutto è sempre davvero utile, se non in casi particolari, ma a livello di conoscenza è bene sapere anche queste cose. Le stringhe sono strumenti potentissimi e Ruby permette una loro agevole e completa manipolazione. E, se vi sembra un po' lungo questo paragrafo, sappiate che qui non abbiamo visto che i concetti più semplici! Ecome spesso avviene, non finisce qui, c'è un altro interessante argomento legato in qualche modo alle stringhe da affrontare....

Eccoci quindi a parlare dei symbols che sono praticamente delle stringhe immutabili (al contrario di quelle classiche - la differenza tra immutabile ed immutabile nell'ambito della programmazione consiste nel fatto sostanzialmente che immutabile può solo essere sovrascritto mentre mutabile può essere modificato) il che garantisce loro dei vantaggi in termini di gestione da parte dell'interprete. Fondamentalmente sono come delle costanti uniche nel programma. I nomi non contengono valori in senso stretto sono dei "punti fissi", anche nel senso della loro locazione fisica come vedremo, all'interno del codice del programma. La loro stabilità è il loro vero punto di forza che, come detto, fa si che il loro uso, in date situazioni, renda molto più veloce il programma.
Da un punto di vista formale vengono introdotto tramite il prefisso : (due punti) che precede il loro nome

:ciao

è un symbol. Esso può contenere qualunque altro carattere proprio di una stringa qualora sia comprensivo dei classici doppi apici.

:"cia/no"

senza apici questo non andrebbe bene. Il concetto di immutabilità rispetto alle stringhe è facilmente espresso nel seguente esempio:

irb(main):039:0> puts "ciao, " << "mondo"
ciao, mondo

irb(main):040:0> puts :"ciao, " << "mondo"
NoMethodError: undefined method `<<' for :"ciao, ":Symbol
from (irb):40
from d:/Ruby200-x64/bin/irb:12:in `<main>'

Una stringa quindi, come abbiamo visto, può essere modificata, se ci provate con un symbol succede il finimondo (per dire, ovviamente...)

E' interessante notare che tra stringhe e symbols esiste una stretta correlazione tanto che è semplice andare da uno all'altro:

irb(main):037:0> "ciao".intern
=> :ciao
irb(main):038:0> :ciao.to_s
=> "ciao"

Un'altra differenza che ci permette di capire la differenza tra queste due entità ce la fornisce il seguente esperimento:

irb(main):044:0> puts "Ciao".object_id
28086520
irb(main):045:0> puts "Ciao".object_id
26723340
irb(main):046:0> puts "Ciao".object_id
24934040
irb(main):047:0> puts :"Ciao".object_id
698508
irb(main):048:0> puts :"Ciao".object_id
698508
irb(main):049:0> puts :"Ciao".object_id
698508 

object_id ci fornisce l'identità dell'oggetto in memoria. Se abbiamo una stringa, sia pure sempre la stessa, l'interprete se la deve in qualche modo cercare, se abbiamo a che fare con un symbol la locazione è sempre quella. Stringhe identiche referenziano differente oggetti in memoria, per i symbols questo non è vero. e simboli identici referenziano la stessa locazione di memoria. Si può dire che con loro l'interprete sa sempre dove andare a cercare. Tra l'altro provate a scrivre il seguente comando, non spaventatevi per l'output un po' esorbitante:

puts Symbol.all_symbols.inspect

Vi risulterà una baraonda di scitte che rappresentano tutti i symbols in uso nel vostro programma. Ogni qual volta ne create uno finisce in quel calderone, che è poi un array. Se già presente e lo ricreate l'interprete usa quello presente.

Anche i simboli hanno numerose proprietà,  che funzionano come per le stringhe, ad es. length:

irb(main):052:0> :"ciao".length
=> 4