Finora il programma non ha detto molto sui tipi, ma chiaramente in Julia esistono:
julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}
Non abbiamo mai specificato i tipi, ma Julia li ha assegnati comunque.
types, che sono centrali per la sua progettazione.Type Inference.Il compilatore JIT esaminerà (tutto) il codice, vedrà come viene usata una variabile e inferirà un tipo predefinito adatto e compatibile con quell'uso.
Affidarsi all'inferenza dei tipi va bene per risolvere semplici esercizi tutorial, ma per programmi più grandi probabilmente ti servirà un controllo più preciso.
Sulla maggior parte dei processori moderni, un intero ha come tipo predefinito Int64.
Abbiamo visto nel concetto Numbers che un valore può essere convertito in un tipo specifico non predefinito.
julia> x = Int16(42)
42
julia> typeof(x)
Int16
Tuttavia, alla variabile x si può ancora assegnare un tipo diverso:
julia> x = "changed"
"changed"
julia> typeof(x)
String
Questa è la type instability, ovvero:
Invece, possiamo impostare il tipo di x usando l'operatore :::
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> y = "changed"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
The function `convert` exists, but no method is defined for this combination of argument types.
Ora y è, e sarà sempre, di tipo Int16.
Quindi il compilatore sa quanti byte riservargli e può ottimizzare il resto del codice facendo affidamento su un tipo stabile.
In questo, la variabile è più o meno simile a quelle di un linguaggio a tipizzazione statica come C.
Int64, Int16, String, Char: da dove «vengono» questi tipi?
In molti linguaggi orientati agli oggetti (OO), ogni tipo è una classe, la creazione di sottoclassi li organizza in una gerarchia di classi e i metodi delle classi definiscono i comportamenti.
Java e Ruby sono esempi evidenti di questo schema, ma anche Python è simile al suo interno.
Julia non ha classi.
La ragione documentata è che le funzionalità OO interferiscono con il compilatore JIT e danneggiano le prestazioni in fase di esecuzione.
Eppure, guarda questo codice:
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> supertype(Int16)
Signed
julia> supertypes(Int16)
(Int16, Signed, Integer, Real, Number, Any)
Aggiungiamo qualche dettaglio:
Int16 è un tipo e possiamo creare variabili di questo tipo.Int16 è un sottotipo di Signed e la funzione supertype() ce lo mostra.Integer, Real e Number fino ad Any in cima, e subtypes() elencherà per noi questo ramo della gerarchia.Tutti i rami terminano in Any, che ha la particolarità di essere il proprio supertipo.
julia> supertypes(String)
(String, AbstractString, Any)
julia> supertype(Any)
Any
Quindi Julia non ha una gerarchia di class, ma ha una gerarchia di type.
Prima o poi proveremo a districare come funziona, ma prima c'è molto altro da esplorare.
Possiamo usare typeof() per verificare l'uguaglianza nel solito modo.
julia> typeof(11)
Int64
julia> typeof(11) == Int64
true
julia> typeof(11) == Number
false
L'uguaglianza tra tipi deve essere esatta, perché questa forma di confronto non comprende la gerarchia dei tipi.
In modo più flessibile, isa ci dirà se un valore ha lo stesso tipo di un comparatore oppure un suo sottotipo.
Può essere usato sia in forma di funzione sia in forma infissa.
julia> 12 isa Int64
true
julia> 12 isa Number
true
julia> isa(12, Number)
true
julia> 12 isa String
false
Nota che isa richiede un value a sinistra, non un type.
Provare a confrontare due tipi in questo modo darà risultati inaspettati.
L'operatore corretto è <:, che vedremo molto di più nei prossimi concetti.
julia> Int64 isa Number ## Don't do this!
false
julia> Int64 <: Number
true
Abbiamo visto che la gerarchia dei tipi forma una struttura ad albero (nel senso dell'informatica, con la radice in cima).
Ogni elemento dell'albero è un node, e questi possono essere divisi in categorie:
abstract.concrete.julia> subtypes(Integer) # an abstract type
3-element Vector{Any}:
Bool
Signed
Unsigned
julia> subtypes(Int64) # a concrete type
Type[]
Questa è una distinzione importante, perché solo i tipi concreti possono essere instantiated come variabili.
julia> a::Int16 = 42
42
julia> typeof(a)
Int16
julia> b::Integer = 42
42
julia> typeof(b)
Int64
Nota che provare a usare un tipo astratto non dà alcun messaggio di errore (in questo caso), ma il compilatore crea un tipo concreto appropriato: Int64 invece di Integer.
In questo esercizio farai un po' di data engineering, preprocessando alcuni dati di valutazione. Una scuola con moltissimi, moltissimi, moltissimi (troppi?) studenti sta cercando di fare un'analisi dei dati sui voti ricevuti dagli studenti e vuole farlo in modo efficiente, quindi l'idea è ridurre e organizzare i dati.
La scala dei voti di solito va da 0 a 10, quindi è molto più efficiente usare un tipo di dato con soli otto bit per voto.
UInt8.Int8.Una volta implementata la funzione per convertire i voti, dovrai scriverne un'altra per gestire le collezioni in cui sono contenuti, declassando i voti e restituendoli in ordine decrescente.
Vector ordinati in modo crescente per memorizzare i voti, perché sono meticolosi con i loro dati.Set, che non sono ordinati, per memorizzare i voti, perché sono un po' più pigri.In entrambe le funzioni dovrai gestire gli input non validi lanciando un MethodError.
La gestione delle eccezioni sarà trattata in un Concept successivo, quindi ai fini di questo esercizio puoi usare la seguente sintassi:
throw(MethodError(f, args))
Dove f è la funzione e args è una tupla degli argomenti passati alla funzione.
Con la funzione demote(n), f è demote e args è (n,):
throw(MethodError(demote, (n,)))
Implementa il metodo demote(n).
Float64, deve arrotondare per eccesso al numero intero più vicino e restituire un tipo di dato UInt8.Integer arbitrario, deve restituire lo stesso intero in un tipo di dato Int8.julia> demote(4.2)::UInt8
5
julia> demote(4)::Int8
4
julia> demote("hi")
MethodError: no method matching demote(::String) #output truncated
Implementa il metodo preprocess(coll).
Vector, deve declassare tutti i numeri e invertire il vettore.Set, deve declassare tutti i numeri e restituire un vettore ordinato in ordine decrescente.julia> preprocess([1, 2, 3])
3-element Vector{Int8}:
3
2
1
julia> preprocess(Set([2.2, 5.8, 3.4]))
3-element Vector{UInt8}:
6
4
3
julia> preprocess(42)
MethodError: no method matching preprocess(::Int64) #output truncated
Iscriviti a Exercism per imparare e padroneggiare Julia con 35 concetti128 esercizi e il mentoring di persone reali, tutto gratis.