Eddig a tanterv nem sokat írt a típusokról, pedig nyilvánvalóan léteznek Juliában:
julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}
Sosem adtuk meg a típusokat, a Julia mégis kiosztotta őket.
types, amelyek központi szerepet játszanak a nyelv felépítésében.Type Inference eszközt használja.A JIT-fordító átnézi az (összes) kódot, megnézi, hogyan használjuk a változót, és kikövetkeztet egy olyan megfelelő alapértelmezett típust, amely összeegyeztethető ezzel a használattal.
A korábbi fogalmakban már sok példát láttunk arra, hogy a numerikus típusok egységessé alakulnak, a legegyszerűbb aritmetikától kezdve.
julia> 2 + 1.3
3.3
Összeadtunk egy egész számot (Int64) és egy lebegőpontos számot (Float64), és az eredmény Float64 lett. Hasonlóképpen:
julia> nums = (3, 4.1, 1//4)
(3, 4.1, 1//4)
julia> typeof(nums)
Tuple{Int64, Float64, Rational{Int64}}
julia> [nums...]
3-element Vector{Float64}:
3.0
4.1
0.25
A fenti esetben a tuple megőrizte az egyes elemek típusát, a vektorrá alakítás viszont mindegyiket egységes Float64-re változtatta.
A Julia fordítója tisztában van vele, mely típuskonverziók lehetségesek: egész számból lebegőpontosba nincs gond, lebegőpontosból egész számba viszont elveszik a pontosság, ezért InexactError hibát kapsz.
A típuspromóció a kifejezés összes értékét egy közös típusra konvertálja, amely elég sokoldalú ahhoz, hogy minden bemenettel kompatibilis legyen.
Ugyanezt a konverziót explicit módon is elvégezheted a promote() függvénnyel:
julia> promote(nums...)
(3.0, 4.1, 0.25)
A típuskövetkeztetésre és a típuspromócióra hagyatkozni rendben van, amikor egyszerű gyakorlófeladatokat oldasz meg, de nagyobb programoknál valószínűleg pontosabb irányításra lesz szükséged.
A legtöbb modern processzoron az egész szám alapértelmezett típusa Int64.
A Numbers fogalomban láttuk, hogy egy érték átalakítható egy adott, nem alapértelmezett típusra.
julia> x = Int16(42)
42
julia> typeof(x)
Int16
Az x változó azonban továbbra is hozzárendelhető egy másik típushoz:
julia> x = "changed"
"changed"
julia> typeof(x)
String
Ez a type instability, ami:
Ehelyett a :: operátorral megadhatod az x típusát:
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> y = "changed"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
The function `convert` exists, but no method is defined for this combination of argument types.
Mostantól y Int16 típusú, és az is marad.
Így a fordító tudja, hány bájtot kell lefoglalnia neki, és a kód többi részét is erre a stabil típusra támaszkodva optimalizálhatja.
Ebben a tekintetben a változó többé-kevésbé hasonlít egy statikusan típusos nyelv, például a C változóihoz.
A fent bemutatott típusmegadást egy változóértékadás bal oldalán használjuk, hogy korlátozzuk a változó típusát.
Ha a :: operátort egy értékkel, vagy egy értékké kiértékelődő dologgal használod, az általában egy állítás arra, hogy az értéknek e típusúnak kell lennie, különben hibát kell dobni.
julia> 42::Number
42
julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String
Ezt gyakran egy függvény visszatérési értékével használják, egyszerű végső ellenőrzésként, hogy a függvény a várt módon viselkedett-e.
Ne feledd, hogy másféle állításokra ott van az @assert makró.
Int64, Int16, String, Char: honnan "származnak" ezek a típusok?
Sok objektumorientált (OO) nyelvben minden típus egy osztály, az alosztályozás osztályhierarchiába rendezi őket, és az osztálymetódusok határozzák meg a viselkedést.
A Java és a Ruby nyilvánvaló példa erre a mintára, de belsőleg még a Python is hasonló.
A Juliában nincsenek osztályok.
A dokumentált indoklás szerint az OO-s jellemzők zavarják a JIT-fordítót, és rontják a futásidejű teljesítményt.
Ki tudja, talán ezt az idézetet is olvasták:
„Az objektumorientált programozás kivételesen rossz ötlet, amely csak Kaliforniából eredhet.”
Edsger Dijkstra nevéhez fűződik, aki az 1950-es évektől kezdve évtizedeken át ragyogó informatikus volt (bár köztudottan nem a napfényes optimizmusáról vagy finom diplomáciájáról híres).
És mégis, nézd meg ezt a kódot:
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> supertype(Int16)
Signed
julia> supertypes(Int16)
(Int16, Signed, Integer, Real, Number, Any)
Néhány részlet kiegészítésképpen:
Int16 egy típus, és ilyen típusú változókat hozhatunk létre.Int16 a Signed altípusa, ezt a supertype() függvény mutatja meg.Integer, Real és Number szintjén át a csúcsig, az Any-ig tart, és a subtypes() kilistázza nekünk a hierarchia ezen ágát.Minden ág az Any-ben végződik, amely abban egyedi, hogy a saját fölérendelt típusa.
julia> supertypes(String)
(String, AbstractString, Any)
julia> supertype(Any)
Any
A Juliában tehát nincs class-hierarchia, type-hierarchiája viszont van.
A teljes hierarchia megjelenítése egy hatalmas fát eredményez, amelyet gyakorlatilag lehetetlen áttekinteni.
Az egyes részeinek megnézése online is szóba került.
A végén majd megpróbáljuk szétszedni, hogyan is működik ez, de előbb még sok mindent fel kell fedeznünk.
A typeof() függvénnyel a szokásos módon vizsgálhatod az egyezést.
julia> typeof(11)
Int64
julia> typeof(11) == Int64
true
julia> typeof(11) == Number
false
A típusegyezésnek pontosnak kell lennie, mert ez az összehasonlítási forma mit sem tud a típusok hierarchiájáról.
Rugalmasabb megoldás az isa, amely megmondja, hogy egy érték típusa megegyezik-e az összehasonlítás alapjául szolgáló típussal, vagy annak altípusa-e.
Használható infix vagy függvényformában is.
julia> 12 isa Int64
true
julia> 12 isa Number
true
julia> isa(12, Number)
true
julia> 12 isa String
false
Ne feledd, hogy az isa bal oldalán value szerepel, nem type.
Ha két típust próbálsz így összehasonlítani, váratlan eredményt kapsz.
A helyes operátor a <:, amellyel a későbbi fogalmakban még sokszor találkozunk.
julia> Int64 isa Number ## Don't do this!
false
julia> Int64 <: Number
true
A típusok vizsgálatával egy függvényen belüli vezérlést is irányíthatsz, de ez az idiomatikus Juliában viszonylag szokatlan.
A Multiple Dispatch fogalomban látni fogjuk, hogy gyakran hatékonyabb típusokat adni a függvényargumentumokhoz, és hagyni, hogy a Julia diszpécser mechanizmusa kezelje az ilyen logikát. Mielőtt azonban eljutunk a Multiple Dispatchig, számos további típusokkal kapcsolatos fogalmat kell megbeszélnünk.
Láttuk, hogy a típusok hierarchiája fastruktúrát alkot (a számítástudomány értelmében, a gyökérrel a csúcson).
A fa minden eleme egy node, és ezek kategóriákba sorolhatók:
abstract típusoknak nevezzük.concrete típusoknak hívjuk.julia> subtypes(Integer) # an abstract type
3-element Vector{Any}:
Bool
Signed
Unsigned
julia> subtypes(Int64) # a concrete type
Type[]
Ez fontos megkülönböztetés, mert változóként csak konkrét típusok instantiated-elhetők.
julia> a::Int16 = 42
42
julia> typeof(a)
Int16
julia> b::Integer = 42
42
julia> typeof(b)
Int64
Figyeld meg, hogy ha absztrakt típust próbálsz használni, az (ebben az esetben) nem ad hibaüzenetet, a fordító viszont létrehoz egy megfelelő konkrét típust: Int64-et Integer helyett.
Legalább megakadályozza, hogy a változóhoz nem egész számot rendelj:
julia> f::Integer = "hello"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Integer
The function `convert` exists, but no method is defined for this combination of argument types.
Az absztrakt típussal végzett típusmegadás tehát megkötést jelent a változó típusára: (a fenti esetben) az Integer bármely altípusát megengedve.
Ez szokatlan a programozási nyelvek világában: gyengébb, mint egy típusmegadás C-ben, és erősebb, mint egy típusutalás a Python újabb verzióiban.
Az isabstracttype() és az isconcretetype() függvényekkel vizsgálódhatsz.
Ne feledd, hogy ezek nem csupán egymás tagadásai: egy későbbi fogalomban látni fogjuk, hogy egyes típusok sem nem absztraktak, sem nem konkrétak.
julia> isconcretetype(Integer), isabstracttype(Integer)
(false, true)
julia> isconcretetype(Int64), isabstracttype(Int64)
(true, false)
# Vector is neither
julia> isconcretetype(Vector), isabstracttype(Vector)
(false, false)
Bár a Vector nem konkrét típus, az elemei konkrétak.
Az eltype() (elemtípus) függvény kinyeri ezt a típust:
julia> eltype([1, 2.3])
Float64