Track
/
Julia
Julia
/
Esercizi
/
Guru delle valutazioni
Guru delle valutazioni

Guru delle valutazioni

Esercizio di apprendimento

Introduzione

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.

  1. Julia ha i types, che sono centrali per la sua progettazione.
  2. Julia di solito riesce a «indovinare» il tipo, usando la 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.

Assegnazione dei tipi

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:

  • Molto comoda negli script piccoli.
  • Dannosa per le prestazioni e l'affidabilità nei programmi più grandi.

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.

La gerarchia dei tipi

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.
  • Esiste una gerarchia di tipi, che risale attraverso 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.

Verificare i tipi

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

Tipi astratti e tipi concreti

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:

  1. I nodi con sottotipi sono chiamati abstract.
  2. I nodi foglia, senza sottotipi, sono chiamati 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.

Istruzioni

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.

  • Alcuni professori usano numeri reali (sono pignoli sulla precisione) e dovrai convertirli nel tipo di dato UInt8.
  • Altri professori usano numeri interi con valori negativi (per punire gli studenti particolarmente indisciplinati) e in questi casi dovrai convertirli nel tipo di dato 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.

  • Alcuni professori usano Vector ordinati in modo crescente per memorizzare i voti, perché sono meticolosi con i loro dati.
  • Altri professori usano 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.

Note

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,)))

1. Declassa i voti

Implementa il metodo demote(n).

  • Con un input Float64, deve arrotondare per eccesso al numero intero più vicino e restituire un tipo di dato UInt8.
  • Con un Integer arbitrario, deve restituire lo stesso intero in un tipo di dato Int8.
  • Tutti gli altri input devono lanciare un MethodError.
julia> demote(4.2)::UInt8
5

julia> demote(4)::Int8
4

julia> demote("hi")
MethodError: no method matching demote(::String)     #output truncated

2. Preprocessa i dati

Implementa il metodo preprocess(coll).

  • Con un input Vector, deve declassare tutti i numeri e invertire il vettore.
  • Con un input Set, deve declassare tutti i numeri e restituire un vettore ordinato in ordine decrescente.
  • Tutti gli altri input devono lanciare un MethodError.
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
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Julia Exercism

Vuoi iniziare Guru delle valutazioni?

Iscriviti a Exercism per imparare e padroneggiare Julia con 35 concetti128 esercizi e il mentoring di persone reali, tutto gratis.