Julia Language
Rodzaje
Szukaj…
Składnia
- niezmienny MyType; pole; pole; koniec
- wpisz MyType; pole; pole; koniec
Uwagi
Rodzaje są kluczem do wydajności Julii. Ważnym pomysłem na wydajność jest stabilność typu , która występuje, gdy typ zwracany przez funkcję zależy tylko od typów, a nie wartości jej argumentów.
Wysyłanie według rodzajów
W Julii możesz zdefiniować więcej niż jedną metodę dla każdej funkcji. Załóżmy, że zdefiniujemy trzy metody tej samej funkcji:
foo(x) = 1
foo(x::Number) = 2
foo(x::Int) = 3
Decydując się, jakiej metody użyć (zwanej wysyłaniem ), Julia wybiera bardziej szczegółową metodę, która pasuje do typów argumentów:
julia> foo('one')
1
julia> foo(1.0)
2
julia> foo(1)
3
Ułatwia to polimorfizm . Na przykład możemy łatwo stworzyć listę połączoną , definiując dwa niezmienne typy, o nazwach Nil
i Cons
. Nazwy te są tradycyjnie używane do opisania odpowiednio pustej listy i niepustej listy.
abstract LinkedList
immutable Nil <: LinkedList end
immutable Cons <: LinkedList
first
rest::LinkedList
end
Będziemy reprezentować pustą listę przez Nil()
i wszelkie inne listy przez Cons(first, rest)
, gdzie first
to pierwszy element listy połączonej, a rest
to lista połączona składająca się ze wszystkich pozostałych elementów. Na przykład lista [1, 2, 3]
będzie reprezentowana jako
julia> Cons(1, Cons(2, Cons(3, Nil())))
Cons(1,Cons(2,Cons(3,Nil())))
Czy lista jest pusta?
Załóżmy, że chcemy rozszerzyć funkcję isempty
biblioteki standardowej, która działa w wielu różnych kolekcjach:
julia> methods(isempty)
# 29 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
...
Możemy po prostu użyć składni funkcji dispatch i zdefiniować dwie dodatkowe metody isempty
. Ponieważ ta funkcja pochodzi z modułu Base
, musimy ją zakwalifikować jako Base.isempty
, aby ją rozszerzyć.
Base.isempty(::Nil) = true
Base.isempty(::Cons) = false
Tutaj wcale nie potrzebowaliśmy wartości argumentów, aby ustalić, czy lista jest pusta. Do obliczenia tej informacji wystarczy sam typ. Julia pozwala nam pominąć nazwy argumentów, zachowując tylko adnotacje typu, jeśli nie musimy używać ich wartości.
Możemy przetestować, czy działają nasze isempty
:
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())))))
i rzeczywiście liczba metod dla isempty
wzrosła o 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
Oczywiste jest, że ustalenie, czy połączona lista jest pusta, czy nie, jest trywialnym przykładem. Ale prowadzi to do czegoś bardziej interesującego:
Jak długa jest lista?
Funkcja length
ze standardowej biblioteki daje nam długość kolekcji lub pewnych iteracji . Istnieje wiele sposobów implementacji length
połączonej listy. W szczególności, za pomocą while
pętli jest prawdopodobnie najszybszy i skuteczny w pamięci Julia najbardziej. Należy jednak unikać przedwczesnej optymalizacji , więc załóżmy na chwilę, że nasza lista nie musi być wydajna. Jaki jest najprostszy sposób na napisanie funkcji length
?
Base.length(::Nil) = 0
Base.length(xs::Cons) = 1 + length(xs.rest)
Pierwsza definicja jest prosta: pusta lista ma długość 0
. Druga definicja jest również łatwa do odczytania: aby policzyć długość listy, liczymy pierwszy element, a następnie liczymy długość reszty listy. Możemy przetestować tę metodę w podobny sposób, jak testowaliśmy 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
Następne kroki
Ten przykład zabawki jest daleki od implementacji wszystkich funkcji, które byłyby pożądane na połączonej liście. Brakuje na przykład interfejsu iteracji. Pokazuje jednak, jak można użyć wysyłki do pisania krótkiego i czytelnego kodu.
Niezmienne typy
Najprostszy typ kompozytu jest niezmienny. Instancje typów niezmiennych, takich jak krotki , są wartościami. Ich pól nie można zmienić po ich utworzeniu. Pod wieloma względami niezmienny typ przypomina Tuple
z nazwami dla samego typu i dla każdego pola.
Rodzaje singletonów
Typy złożone z definicji zawierają wiele prostszych typów. W Julii liczba ta może wynosić zero; oznacza to, że niezmienny typ nie może zawierać żadnych pól. Jest to porównywalne z pustą krotką ()
.
Dlaczego może to być przydatne? Takie niezmienne typy są znane jako „typy singletonów”, ponieważ tylko jedna z nich może istnieć. Wartości tego typu są znane jako „wartości singletonowe”. Standardowa Base
biblioteczna zawiera wiele takich typów singletonów. Oto krótka lista:
-
Void
, rodzajnothing
. Możemy sprawdzić, czyVoid.instance
(która jest specjalną składnią do pobierania wartości singletonu typu singleton) jest w rzeczywistościnothing
. - Każdy typ nośnika, taki jak
MIME"text/plain"
, jest typem singleton z pojedynczą instancją,MIME("text/plain")
. -
Irrational{:π}
,Irrational{:e}
,Irrational{:φ}
i podobne typy są typami singletonów, a ich instancjami singletonu są wartości irracjonalneπ = 3.1415926535897...
itd. - Cechy rozmiaru iteratora
Base.HasLength
,Base.HasShape
,Base.IsInfinite
iBase.SizeUnknown
są typami singletonów.
- W wersji 0.5 i późniejszych każda funkcja jest instancją singletonu typu singleton! Jak każda inna wartość singletonu, możemy odzyskać funkcję
sin
, na przykład ztypeof(sin).instance
.
Ponieważ nie zawierają niczego, typy singletonów są niezwykle lekkie i często mogą być optymalizowane przez kompilator, aby nie generować narzutu w czasie wykonywania. Są zatem idealne do cech, specjalnych wartości znaczników i do funkcji, na których chcielibyśmy się specjalizować.
Aby zdefiniować typ singletonu,
julia> immutable MySingleton end
Aby zdefiniować niestandardowe drukowanie dla typu singletonu,
julia> Base.show(io::IO, ::MySingleton) = print(io, "sing")
Aby uzyskać dostęp do instancji singleton,
julia> MySingleton.instance
MySingleton()
Często przypisuje się to do stałej:
julia> const sing = MySingleton.instance
MySingleton()
Rodzaje opakowań
Jeśli niezmienne typy zerowe są interesujące i przydatne, być może typy niezmienne jednolowe są jeszcze bardziej przydatne. Takie typy są powszechnie nazywane „typami opakowań”, ponieważ obejmują niektóre podstawowe dane, zapewniając alternatywny interfejs dla tych danych. Przykładem typu opakowania w Base
jest String
. Zdefiniujemy typ podobny do String
, o nazwie MyString
. Ten typ będzie wspierany przez wektor (jednowymiarowa tablica ) bajtów ( UInt8
).
Po pierwsze, sama definicja typu i niektóre niestandardowe pokazywanie:
immutable MyString <: AbstractString
data::Vector{UInt8}
end
function Base.show(io::IO, s::MyString)
print(io, "MyString: ")
write(io, s.data)
return
end
Teraz nasz typ MyString
jest gotowy do użycia! Możemy podać trochę surowych danych UTF-8 i wyświetla się tak, jak chcemy:
julia> MyString([0x48,0x65,0x6c,0x6c,0x6f,0x2c,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21])
MyString: Hello, World!
Oczywiście ten typ ciągu wymaga dużo pracy, zanim stanie się tak użyteczny jak typ Base.String
.
Prawdziwe typy kompozytowe
Być może najczęściej wiele niezmiennych typów zawiera więcej niż jedno pole. Przykładem jest standardowa biblioteka typu Rational{T}
, która zawiera dwa pola: pole num
dla licznika i pole den
dla mianownika. Emulowanie tego typu projektu jest dość proste:
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)
Z powodzeniem wdrożyliśmy konstruktor, który upraszcza nasze liczby wymierne:
julia> MyRational(10, 6)
MyRational{Int64}(5,3)