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 , rodzaj nothing . Możemy sprawdzić, czy Void.instance (która jest specjalną składnią do pobierania wartości singletonu typu singleton) jest w rzeczywistości nothing .
  • 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 i Base.SizeUnknown są typami singletonów.
0,5.0
  • 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 z typeof(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)


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow