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 di nothing . Possiamo verificare che Void.instance (che è una sintassi speciale per il recupero del valore singleton di un tipo singleton) non è davvero nothing .
  • 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 e Base.SizeUnknown sono tutti tipi di singleton.
0.5.0
  • 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, da typeof(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)


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow