Tí

Típusok ebben a kurzusban: Julia

1 feladat

A(z) Típusok fogalomról

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.

  1. A Juliában vannak types, amelyek központi szerepet játszanak a nyelv felépítésében.
  2. A Julia a típust általában „ki is találja”, ehhez pedig a 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.

Típuspromóció

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)

Típusmegadás

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:

  • Nagyon kényelmes kis szkripteknél.
  • Nagyobb programoknál rontja a teljesítményt és a megbízhatóságot.

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.

Típusállítás

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ó.

A típusok hierarchiája

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:

  • Az Int16 egy típus, és ilyen típusú változókat hozhatunk létre.
  • Az Int16 a Signed altípusa, ezt a supertype() függvény mutatja meg.
  • A típusok hierarchiát alkotnak, amely 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.

Típusok vizsgálata

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.

Absztrakt és konkrét típusok

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:

  1. Az altípusokkal rendelkező csomópontokat abstract típusoknak nevezzük.
  2. A leveleket, amelyeknek nincs altípusuk, 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
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Típusok fogalmat