Julia



Type System - basi          

Un corposo paragrafo della documentazione ufficiale è devoluto a questo importante argomento.

In realtà avremmo forse dovuto inserire questo paragrafo prima di introdurre numeri, caratteri e stringhe ma abbiamo preferito permettere a chi voleva, di avere da subito in mano degli strumenti per poter lavorare con tipi di dati comuni e di semplice comprensione visto che i concetti qui espressi non impedivano certo la piena possibilità di utilizzare numeri, lettere ecc...

Julia è un linguaggio a tipizzazione dinamica il che significa che bisogna attendere le elaborazioni compiute a runtime. Tuttavia in alcuni casi è possibile specificare un tipo preciso di appartenenza per una variabile in questo senso usufruendo dei vantaggi della tipizzazione statica in particolare per quanto riguarda gli aspetti prestazionali. Il codice in Julia è molto libero in generale. Il fatto di non dover specificare il tipo lascia molto spazio di manovra in questo ambito che viene mantenuto finchè possibile.  In questo linguaggio tutti i tipi sono finali, non ci sono sottotipi e un predecessore può essere solo un tipo astratto. Quello che conta non è l'ereditare una struttura ma il comportamento di un tipo. Valgono le seguenti regole generali:

  ++  tutti i valori in Julia sono veri oggetti che appartengono ad un determinato tipo le cui, chiamiamole foglie, sono tutti oggetti di pari livello.
  ++  Solo i valori hanno un tipo. Le variabili sono nomi che identificano la locazione di un valore
  ++  Il tipo di un valore è definito a runtime. Non esiste il concetto di tipo a compile time
  ++  Tipi concreti e astratti possono essere parametrizzati da altri tipi e da alcuni valori

Queste regole in realtà costituiscono una base teorica che, all'atto pratico, vi risulterà naturale.

Una prima cosa importante da annotare è che possiamo modificare, se davvero conviene, la natura dinamica di Julia. L'operatore per l'attribuzione di un tipo esplicito è :: che viene letto come "istanza di", il che collima col concetto che tutti i valori in questo linguaggio sono oggetti, come indicato nel primo punto dell'elenco precedente. Non è una operazione di assegnamento ma una asserzione relativa a quella variabile; ne fissiamo il comportamento per tutta la durata del programma. L'attribuzione del tipo deve essere fatta in modo coerente in base ad un eventuale valore di inizializzazione, banalmente:

julia> 1::Int
1

julia> 1::FloatingPoint
ERROR: type: typeassert: expected FloatingPoint, got Int64

Il primo esempio va bene il secondo presenta un errore perchè l'assegnazione è evidentemente incongrua tra valore e tipo.
Se invece attacchiamo il tipo ad una variabile questo vuole dire che quella variabile avrà valori appartenenti al tipo per tutta la durata del programma, fissando quindi i limiti, o meglio l'ambito, da rispettare. Peraltro è da sottolineare che non è possibile fissare un tipo per una variabile globale ma è possibile farlo solo all'interno di una funzione. E' un punto da ricordare, da ricrodare, peraltro l'interprete si fa sentire anche se in maniera a mio avviso un po' ambigua; difatti l'errore esposto nel caso scriveste una cosa tipo:

x::int8 = 9

è il seguente:

ERROR: x not defined

non il massimo della chiarezza.

Assegnare un tipo ad una variabile non solo garantisce che la tipologia sarà costante per tutto il programma, come detto, ma anche che ogni valore assegnato alla stessa sarà convertito nella maniera opportuna usando la funzione convert a condizione che la conversione sia direttamente possibile. La possibilità appena illustrata permette quindi, in situazioni particolari, di avvantaggiarci delle peculiarità di una dichiarazione statica invece che dinamica; questo senza dimenticare che Julia è ottimizzato per il dinamismo....

Julia prevede anche l'esistenza, come abbiamo accennato a inizio paragrafo, dei tipi astratti. Essi, come ci fa intuire quell'aggettivo "astratti", non possono essere istanziati, usati direttamente, ma sono degli step intermedi in un grafo di definizione di particolare tipo. Nonostante questa apparente scarsa utilità essi costituiscono un aspetto importante del type system di questo linguaggio. I tipi astratti servono come base per quelli da essi derivati definendo dei comportamenti comuni.
I tipi astratti vengono definiti tramite la keyword abstract nelle maniere seguenti:

1) abstract nometipo
2) abstract nometipo <: supertipo


Il primo metodo dichiara semplicemente un nuovo tipo astratto mentre il 2) dichiara un nuovo tipo e ed esprime la sua discendenza dal tipo "supertipo". L'operatore da usare in questo caso è evidentemente <: . In quest'ultimo caso il nuovo tipo astratto è un sottotipo del supertipo. Un esempio riferito al type system nativo in Julia e riportato sulla documentazione on line è il seguente:

abstract Number
abstract Real <: Number
abstract FloatingPoint <: Real
abstract Integer <: Real
abstract Signed <: Integer
abstract Unsigned <: Integer


Naturalmente nulla vieta di crearsi i propri tipi e stabilire una gerarchia anche più complessa. Lo scopo è proprio quello.

Molto interessanti sono anche i bits type. Essi sono definiti in modo da specificare la loro dimensione in bit. Sono molto usati all'interno di Julia stessa nella definizione delle tipologie numeriche. La keyword è, guarda un po', bitstype:

bitstype numerodibits: nome
bitstype numerodibits: nome <: supertipo


Parecchi esempi li possiamo appunto ricavare dal type system di Julia:

bitstype 16 Float16 <: FloatingPoint
bitstype 32 Int32 <: Signed

Possiamo vedere la definizione dei float16 e degli int32 come discendenti di FloatingPoint e di Signed.

Queste sono le basi per cercare penetrare nei meandri del type system di questo bel linguaggio. Nei prossimi paragrafi osserveremo più in dettaglio alcune tipologie interessanti.