Julia Language
tipi
Ricerca…
Sintassi
- MyType immutabile; campo; campo; fine
- digitare MyType; campo; campo; fine
Osservazioni
I tipi sono fondamentali per la performance di Julia. Un'idea importante per le prestazioni è la stabilità del tipo , che si verifica quando il tipo restituito da una funzione dipende solo dai tipi, non dai valori, dei suoi argomenti.
Dispacciamento su Tipi
Su Julia, puoi definire più di un metodo per ogni funzione. Supponiamo di definire tre metodi con la stessa funzione:
foo(x) = 1
foo(x::Number) = 2
foo(x::Int) = 3
Al momento di decidere quale metodo usare (chiamato dispatch ), Julia sceglie il metodo più specifico che corrisponde ai tipi degli argomenti:
julia> foo('one')
1
julia> foo(1.0)
2
julia> foo(1)
3
Questo facilita il polimorfismo . Ad esempio, possiamo facilmente creare un elenco collegato definendo due tipi immutabili, denominati Nil
e Cons
. Questi nomi sono tradizionalmente usati per descrivere rispettivamente una lista vuota e una lista non vuota.
abstract LinkedList
immutable Nil <: LinkedList end
immutable Cons <: LinkedList
first
rest::LinkedList
end
Rappresenteremo la lista vuota di Nil()
e qualsiasi altra lista di Cons(first, rest)
, dove first
è il primo elemento dell'elenco collegato e rest
è l'elenco collegato costituito da tutti gli elementi rimanenti. Ad esempio, la lista [1, 2, 3]
sarà rappresentata come
julia> Cons(1, Cons(2, Cons(3, Nil())))
Cons(1,Cons(2,Cons(3,Nil())))
L'elenco è vuoto?
Supponiamo di voler estendere la funzione isempty
della libreria standard, che funziona su una varietà di collezioni diverse:
julia> methods(isempty)
# 29 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
...
Possiamo semplicemente usare la sintassi dispatch della funzione e definire due metodi aggiuntivi di isempty
. Poiché questa funzione è dal modulo Base
, dobbiamo qualificarla come Base.isempty
per estenderla.
Base.isempty(::Nil) = true
Base.isempty(::Cons) = false
Qui, non abbiamo affatto bisogno dei valori degli argomenti per determinare se l'elenco è vuoto. Solo il tipo da solo è sufficiente per calcolare tali informazioni. Julia ci consente di omettere i nomi degli argomenti, mantenendo solo la loro annotazione del tipo, se non è necessario utilizzare i loro valori.
Possiamo verificare che i nostri metodi isempty
funzionino:
julia> using Base.Test
julia> @test isempty(Nil())
Test Passed
Expression: isempty(Nil())
julia> @test !isempty(Cons(1, Cons(2, Cons(3, Nil()))))
Test Passed
Expression: !(isempty(Cons(1,Cons(2,Cons(3,Nil())))))
e in effetti il numero di metodi per isempty
è aumentato di 2
:
julia> methods(isempty)
# 31 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
Chiaramente, determinare se un elenco collegato è vuoto o meno è un esempio banale. Ma porta a qualcosa di più interessante:
Quanto dura la lista?
La funzione di length
della libreria standard ci fornisce la lunghezza di una raccolta o di determinati iterabili . Esistono molti modi per implementare la length
per un elenco collegato. In particolare, usare un ciclo while
è probabilmente il più veloce e più efficiente in termini di memoria in Julia. Ma l'ottimizzazione prematura deve essere evitata, quindi supponiamo per un secondo che il nostro elenco collegato non sia efficiente. Qual è il modo più semplice per scrivere una funzione di length
?
Base.length(::Nil) = 0
Base.length(xs::Cons) = 1 + length(xs.rest)
La prima definizione è semplice: una lista vuota ha lunghezza 0
. Anche la seconda definizione è facile da leggere: per contare la lunghezza di una lista, contiamo il primo elemento, quindi contiamo la lunghezza del resto della lista. Possiamo testare questo metodo in modo simile al modo in cui abbiamo provato l' isempty
:
julia> @test length(Nil()) == 0
Test Passed
Expression: length(Nil()) == 0
Evaluated: 0 == 0
julia> @test length(Cons(1, Cons(2, Cons(3, Nil())))) == 3
Test Passed
Expression: length(Cons(1,Cons(2,Cons(3,Nil())))) == 3
Evaluated: 3 == 3
Prossimi passi
Questo esempio di giocattolo è abbastanza lontano dall'implementazione di tutte le funzionalità che sarebbero desiderate in una lista collegata. Manca, per esempio, l'interfaccia di iterazione. Tuttavia, illustra come la spedizione può essere utilizzata per scrivere codice breve e chiaro.
Tipi immutabili
Il tipo di composito più semplice è di tipo immutabile. Le istanze di tipi immutabili, come le tuple , sono valori. I loro campi non possono essere modificati dopo la loro creazione. In molti modi, un tipo immutabile è come una Tuple
con nomi per il tipo stesso e per ogni campo.
Tipi Singleton
I tipi di composito, per definizione, contengono un numero di tipi più semplici. In Julia, questo numero può essere zero; vale a dire, un tipo immutabile, è permesso di contenere i campi. Questo è paragonabile alla tupla vuota ()
.
Perché potrebbe essere utile? Tali tipi immutabili sono noti come "tipi singleton", in quanto solo una loro istanza potrebbe mai esistere. I valori di tali tipi sono noti come "valori singleton". La libreria standard Base
contiene molti tali tipi Singleton. Ecco una breve lista:
-
Void
, il tipo dinothing
. Possiamo verificare cheVoid.instance
(che è una sintassi speciale per il recupero del valore singleton di un tipo singleton) non è davveronothing
. - Qualsiasi tipo di supporto, come
MIME"text/plain"
, è un tipo singleton con una sola istanza,MIME("text/plain")
. - L'
Irrational{:π}
,Irrational{:e}
,Irrational{:φ}
, e tipi simili sono tipi singleton, e le loro istanze Singleton sono i valori irrazionaliπ = 3.1415926535897...
, ecc. - I tratti della dimensione iteratore
Base.HasLength
,Base.HasShape
,Base.IsInfinite
eBase.SizeUnknown
sono tutti tipi di singleton.
- Nella versione 0.5 e successive, ogni funzione è un'istanza singleton di un tipo singleton! Come ogni altro valore singleton, possiamo recuperare la funzione
sin
, ad esempio, datypeof(sin).instance
.
Poiché non contengono nulla, i tipi di singleton sono incredibilmente leggeri e possono essere spesso ottimizzati dal compilatore per non avere sovraccarico di runtime. Pertanto, sono perfetti per i tratti, i valori speciali dei tag e per funzioni come le funzioni su cui ci si vuole specializzare.
Per definire un tipo singleton,
julia> immutable MySingleton end
Per definire la stampa personalizzata per il tipo singleton,
julia> Base.show(io::IO, ::MySingleton) = print(io, "sing")
Per accedere all'istanza singleton,
julia> MySingleton.instance
MySingleton()
Spesso, si assegna questo a una costante:
julia> const sing = MySingleton.instance
MySingleton()
Tipi di wrapper
Se i tipi immutabili a campo zero sono interessanti e utili, allora forse i tipi immutabili a un campo sono ancora più utili. Tali tipi vengono comunemente chiamati "tipi di wrapper" perché racchiudono alcuni dati sottostanti, fornendo un'interfaccia alternativa a tali dati. Un esempio di un tipo di wrapper in Base
è String
. Definiremo un tipo simile a String
, chiamato MyString
. Questo tipo sarà supportato da un vettore ( array monodimensionale) di byte ( UInt8
).
Innanzitutto, la definizione del tipo stesso e alcuni risultati personalizzati:
immutable MyString <: AbstractString
data::Vector{UInt8}
end
function Base.show(io::IO, s::MyString)
print(io, "MyString: ")
write(io, s.data)
return
end
Ora il nostro tipo di MyString
è pronto per l'uso! Possiamo alimentarlo con dati UTF-8 grezzi e viene visualizzato come ci piace:
julia> MyString([0x48,0x65,0x6c,0x6c,0x6f,0x2c,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21])
MyString: Hello, World!
Ovviamente, questo tipo di stringa richiede molto lavoro prima che diventi utilizzabile come il tipo Base.String
.
Veri tipi di composito
Forse più comunemente, molti tipi immutabili contengono più di un campo. Un esempio è la libreria standard Rational{T}
type, che contiene due fieds: un campo num
per il numeratore e un campo den
per il denominatore. È abbastanza semplice emulare questo tipo di progettazione:
immutable MyRational{T}
num::T
den::T
MyRational(n, d) = (g = gcd(n, d); new(n÷g, d÷g))
end
MyRational{T}(n::T, d::T) = MyRational{T}(n, d)
Abbiamo implementato con successo un costruttore che semplifica i nostri numeri razionali:
julia> MyRational(10, 6)
MyRational{Int64}(5,3)