Julia Language
Types
Zoeken…
Syntaxis
- onveranderlijk MyType; veld; veld; einde
- typ MyType; veld; veld; einde
Opmerkingen
Typen zijn de sleutel tot Julia's uitvoering. Een belangrijk idee voor prestaties is type stabiliteit , die optreedt wanneer het type dat een functie retourneert alleen afhankelijk is van de typen, niet de waarden, van de argumenten.
Verzenden op typen
Op Julia kunt u meer dan één methode voor elke functie definiëren. Stel dat we drie methoden met dezelfde functie definiëren:
foo(x) = 1
foo(x::Number) = 2
foo(x::Int) = 3
Bij het beslissen welke methode ze moet gebruiken ( verzending genoemd ), kiest Julia de meer specifieke methode die overeenkomt met de typen argumenten:
julia> foo('one')
1
julia> foo(1.0)
2
julia> foo(1)
3
Dit vergemakkelijkt polymorfisme . We kunnen bijvoorbeeld eenvoudig een gekoppelde lijst maken door twee onveranderlijke typen te definiëren, genaamd Nil
en Cons
. Deze namen worden traditioneel gebruikt om respectievelijk een lege lijst en een niet-lege lijst te beschrijven.
abstract LinkedList
immutable Nil <: LinkedList end
immutable Cons <: LinkedList
first
rest::LinkedList
end
We zullen de lege lijst weergeven door Nil()
en alle andere lijsten door Cons(first, rest)
, waarbij first
het eerste element van de gekoppelde lijst is en rest
de gekoppelde lijst die bestaat uit alle resterende elementen. De lijst [1, 2, 3]
wordt bijvoorbeeld weergegeven als
julia> Cons(1, Cons(2, Cons(3, Nil())))
Cons(1,Cons(2,Cons(3,Nil())))
Is de lijst leeg?
Stel dat we de isempty
functie van de standaardbibliotheek willen uitbreiden, die op verschillende collecties werkt:
julia> methods(isempty)
# 29 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
...
We kunnen eenvoudig de syntaxis van de functie verzending gebruiken en twee extra methoden van isempty
. Aangezien deze functie afkomstig is van de Base
, moeten we deze kwalificeren als Base.isempty
om deze uit te breiden.
Base.isempty(::Nil) = true
Base.isempty(::Cons) = false
Hier hadden we de argumentwaarden helemaal niet nodig om te bepalen of de lijst leeg is. Alleen het type alleen is voldoende om die informatie te berekenen. Julia staat ons toe de namen van argumenten weg te laten en alleen hun type-annotatie te behouden als we hun waarden niet hoeven te gebruiken.
We kunnen testen dat onze isempty
methoden werken:
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())))))
en inderdaad is het aantal methoden voor isempty
met 2
toegenomen:
julia> methods(isempty)
# 31 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
Het is duidelijk dat het een triviaal voorbeeld is om te bepalen of een gekoppelde lijst leeg is of niet. Maar het leidt tot iets interessants:
Hoe lang is de lijst?
De length
functie van de standaard bibliotheek geeft ons de lengte van een verzameling of bepaalde iterables . Er zijn veel manieren om length
te implementeren voor een gekoppelde lijst. Met name het gebruik van een while
lus is waarschijnlijk het snelst en het meest geheugenefficiënt in Julia. Maar voortijdige optimalisatie moet worden vermeden, dus laten we even veronderstellen dat onze gekoppelde lijst niet efficiënt hoeft te zijn. Wat is de eenvoudigste manier om een schrijven length
functie?
Base.length(::Nil) = 0
Base.length(xs::Cons) = 1 + length(xs.rest)
De eerste definitie is eenvoudig: een lege lijst heeft lengte 0
. De tweede definitie is ook gemakkelijk te lezen: om de lengte van een lijst te tellen, tellen we het eerste element en tellen we vervolgens de lengte van de rest van de lijst. We kunnen deze methode op dezelfde manier testen als hoe we isempty
hebben getest:
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
Volgende stappen
Dit speelgoedvoorbeeld is vrij verre van het implementeren van alle functionaliteit die gewenst zou zijn in een gekoppelde lijst. Het ontbreekt bijvoorbeeld aan de iteratie-interface. Het illustreert echter hoe verzending kan worden gebruikt om korte en duidelijke code te schrijven.
Onveranderlijke typen
Het eenvoudigste composiettype is een onveranderlijk type. Gevallen van onveranderlijke typen, zoals tupels , zijn waarden. Hun velden kunnen niet worden gewijzigd nadat ze zijn gemaakt. In veel opzichten is een onveranderlijk type als een Tuple
met namen voor het type zelf en voor elk veld.
Singleton-typen
Samengestelde typen bevatten per definitie een aantal eenvoudigere typen. In Julia kan dit getal nul zijn; dat wil zeggen, een onveranderlijk type mag geen velden bevatten. Dit is vergelijkbaar met de lege tuple ()
.
Waarom kan dit nuttig zijn? Dergelijke onveranderlijke typen staan bekend als 'singleton-typen', aangezien er slechts één exemplaar van zou kunnen bestaan. De waarden van dergelijke typen staan bekend als "singleton-waarden". De standaard bibliotheek Base
bevat veel van dergelijke Singleton types. Hier is een korte lijst:
-
Void
, het soortnothing
. We kunnen verifiëren datVoid.instance
(wat een speciale syntaxis is voor het ophalen van de singleton-waarde van een singleton-type) inderdaadnothing
. - Elk mediatype, zoals
MIME"text/plain"
, is een singleton-type met een enkele instantie,MIME("text/plain")
. - De
Irrational{:π}
,Irrational{:e}
,Irrational{:φ}
en vergelijkbare typen zijn singleton-typen en hun singleton-instanties zijn de irrationele waardenπ = 3.1415926535897...
, enz. - De iterator-maat
Base.HasLength
,Base.HasShape
,Base.IsInfinite
enBase.SizeUnknown
zijn allemaal singleton-typen.
- In versie 0.5 en hoger is elke functie een singletoninstantie van het type singleton! Net als elke andere singleton-waarde kunnen we de functie
sin
bijvoorbeeld herstellen vantypeof(sin).instance
.
Omdat ze niets bevatten, zijn singleton-typen ongelooflijk licht en kunnen ze vaak door de compiler worden geoptimaliseerd om geen runtime overhead te hebben. Ze zijn dus perfect voor eigenschappen, speciale tagwaarden en voor dingen zoals functies waarop men zich zou willen specialiseren.
Om een singleton-type te definiëren,
julia> immutable MySingleton end
Om aangepast afdrukken voor het type singleton te definiëren,
julia> Base.show(io::IO, ::MySingleton) = print(io, "sing")
Om toegang te krijgen tot de singleton-instantie,
julia> MySingleton.instance
MySingleton()
Vaak wijst men dit toe aan een constante:
julia> const sing = MySingleton.instance
MySingleton()
Wrapper types
Als onveranderlijke typen met nul velden interessant en nuttig zijn, zijn wellicht onveranderlijke typen met één veld nog nuttiger. Dergelijke typen worden gewoonlijk "omhullingsoorten" genoemd omdat ze sommige onderliggende gegevens omwikkelen, waardoor een alternatieve interface voor genoemde gegevens wordt verschaft. Een voorbeeld van een wrapper-type in Base
is String
. We zullen een soortgelijk type definiëren als String
, genaamd MyString
. Dit type wordt ondersteund door een vector (eendimensionale array ) bytes ( UInt8
).
Eerst de typedefinitie zelf en een aantal aangepaste weergaven:
immutable MyString <: AbstractString
data::Vector{UInt8}
end
function Base.show(io::IO, s::MyString)
print(io, "MyString: ")
write(io, s.data)
return
end
Nu is ons MyString
type klaar voor gebruik! We kunnen het wat onbewerkte UTF-8-gegevens geven en het wordt weergegeven zoals we willen:
julia> MyString([0x48,0x65,0x6c,0x6c,0x6f,0x2c,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21])
MyString: Hello, World!
Het is duidelijk dat dit Base.String
veel werk vereist voordat het net zo bruikbaar wordt als het Base.String
type.
Echte composiettypen
Misschien zijn de meeste onveranderlijke typen meer dan één veld. Een voorbeeld is het standaardbibliotheektype Rational{T}
, dat twee velden bevat: een num
veld voor de teller en een den
veld voor de noemer. Het is vrij eenvoudig om dit typeontwerp te emuleren:
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)
We hebben met succes een constructor geïmplementeerd die onze rationale getallen vereenvoudigt:
julia> MyRational(10, 6)
MyRational{Int64}(5,3)