A tantervben idáig jutva már rengeteg függvénydefinícióval találkoztál. Ezeket általában anélkül mutattuk be, hogy sokat foglalkoztunk volna azzal, mit ír ki a REPL:
julia> f(x) = x^2
f (generic function with 1 method)
Miért jelenik meg az, hogy "generic function with 1 method"?
Tegyük fel, hogy pontosabban szeretnénk megadni az argumentumok típusát.
julia> f(x::Integer) = x^2
f (generic function with 2 methods)
julia> f(x::Real) = x^2
f (generic function with 3 methods)
Vagy megváltoztathatjuk az argumentumok számát:
julia> f(x, y) = x * y
f (generic function with 4 methods)
Úgy tűnik, hogy ugyanazon függvény új definícióit „metódusoknak” nevezzük. A metódusok bekerülnek egy listába, ahelyett hogy felülírnék a régi definíciót.
A metódusokat könnyedén listázhatjuk:
julia> methods(f) # output edited for brevity
# 4 methods for generic function "f" from Main:
[1] f(x, y)
[2] f(x::Integer)
[3] f(x::Real)
[4] f(x)
Ha meghívjuk az f függvényt, a Julia valahogy tudja, melyik metódust kell használnia:
julia> f(2.5) # Float64
6.25
julia> f(2) # Int64
4
julia> f(2, 3) # Int64, Int64
6
A dokumentáció szerint a függvény egy objektum, amely argumentumok egy tuple-jét egy visszatérési értékhez rendeli.
Az f(2, 3) függvényargumentumaira általában két különálló dologként gondolunk, a fordító viszont egyetlen tuple-ként látja őket. Ez egyetlen argumentum esetén is igaz (bár ahhoz, hogy ez világosabb legyen, vesszőt kell tennünk):
julia> typeof( (2, 3) )
Tuple{Int64, Int64}
julia> typeof( (2,) )
Tuple{Int64}
Ha a függvényt fekete dobozként szemléljük, azt mondhatjuk, hogy egy tuple megy be, és egy visszatérési érték jön ki. Formálisabban megfogalmazva a tuple hozzá van rendelve egy visszatérési értékhez.
Ezt általában „polimorfizmusnak” nevezik.
Ez már évtizedek óta fontos téma a számítástudományban, és többféle megközelítést dolgoztak ki rá.
A Julia megközelítése szokatlan, de nem egyedülálló.
+ operátor jelenleg 161 metódussal rendelkező függvény)...., ezt részben megkerülheti).dispatched-re a megadott argumentumokkal, és egy eredményt ad vissza.MethodError hibát dob.Figyeld meg, hogy a Julia dinamikusan típusos, ezért a metódusok kiválasztása futási időben történik.
Az előző szakaszban sok szó esett róla, úgyhogy most ültessük át ezeket az elképzeléseket kódba. Az egyszerűség kedvéért az alábbi függvényeknek csak egyetlen argumentumuk van, de nem ritka, hogy egy függvénynek több tucat argumentuma van (például az ábrázolócsomagokban vagy a gépi tanulásban).
Kezdjük azzal, hogy definiálunk néhány saját típust a geometriai alakzatok ábrázolására.
julia> abstract type Shape end
julia> struct Circle <: Shape
radius::Float64
end
julia> struct Square <: Shape
side::Float64
end
julia> struct Rectangle <: Shape
length::Float64
width::Float64
end
Ezután definiálhatunk metódusokat az egyes alakzatok területének kiszámítására. Mindegyik egy argumentumot vár, amely minden esetben más konkrét, felhasználó által definiált típusú.
julia> area(c::Circle) = π * c.radius^2
area (generic function with 1 method)
julia> area(s::Square) = s.side^2
area (generic function with 2 methods)
julia> area(r::Rectangle) = r.length * r.width
area (generic function with 3 methods)
Most már létrehozhatunk néhány alakzatot, és kiszámíthatjuk a területeket, bízva abban, hogy a Julia diszpécselési mechanizmusa mindegyikhez a megfelelő képletet választja.
julia> circle = Circle(2.3)
Circle(2.3)
julia> area(circle) # call signature Tuple{Circle}
16.619025137490002
julia> square = Square(1.6)
Square(1.6)
julia> area(square) # call signature Tuple{Square}
2.5600000000000005
julia> rectangle = Rectangle(1.4, 2.1)
Rectangle(1.4, 2.1)
julia> area(rectangle) # call signature Tuple{Rectangle}
2.94
A metódusszignatúrák fogadhatnak dummy típusértékeket, nagyjából ugyanúgy, ahogy a parametrikus típusok.
A szintaxis jellegzetes: where záradékot igényel:
julia> same_type(v::Vector{T}, x::T) where {T} = true
same_type (generic function with 1 method)
julia> same_type([1, 2], 3)
true
julia> same_type([1, 2], "three")
ERROR: MethodError: no method matching same_type(::Vector{Int64}, ::String)
A fenti példában mindkét argumentumnak ugyanazt a T típust kell használnia, bármi is legyen az.
Pontosabbak lehetünk, ha megkötést teszünk a T-re:
julia> same_type(v::Vector{T}, x::T) where {T<:Number} = true
same_type (generic function with 2 methods)
Ezzel a metódussal csak az azonos numerikus típusok adnak vissza true-t.
Több dummy változó is lehetséges, amelyeket vesszővel elválasztva sorolunk fel a where záradékban.
julia> myfunc(a::S, b::T) where {S, T} = true
myfunc (generic function with 1 method)
A többszörös diszpécselés rendkívül rugalmas és nagyon gyors a Juliában, ezért széles körben használják.
Ahogy a + 161 metódusára vonatkozó példa is mutatja, a Julia-programozók igyekeznek szűken meghatározott típusokkal definiálni a metódusokat.
Két egész szám összeadása bájtkód szinten nagyon más, mint két lebegőpontos számé, ráadásul ha egy egész számot adunk egy lebegőpontoshoz, az a típuspromóció további bonyodalmát hozza magával.
Minél konkrétabb a típus, annál több optimalizálást tud végezni a fordító: egy kis Exercism-feladatban alig számít, de elengedhetetlen a teljesítményhez azokban a nagy numerikus szimulációkban, amelyekre a Juliát tervezték.
Ha tudod, milyen bemeneteket kap majd egy metódus, mondd meg a fordítónak!
Ebben a feladatban a találkozások szimulációját fogod megvalósítani. Ez megismertet a többszörös diszpécselés alapjaival, ami a Julia fő paradigmája.
For this exercise, all function definitions should be a single-line declaration with no if/else logic.
Általában a találkozások során egy a entitás találkozik egy másik b entitással, és reagál rá.
encounter(a, b) = "$(name(a)) meets $(name(b)) and $(meets(a, b))."
Először is szimuláljuk, mi történik, amikor macskák és kutyák találkoznak.
Először adj hozzá egy absztrakt Pet típust.
Ezután definiáld a Cat és Dog típusokat a Pet altípusaiként.
Mindegyiknek egyetlen name mezője van.
Valósítsd meg a name() függvényt, amely visszaadja a háziállat nevét.
julia> fido = Dog("Fido")
Dog("Fido")
julia> name(fido)
"Fido"
Valósítsd meg a meets() metódusokat a következő találkozásokhoz:
julia> ginger = Cat("Ginger")
Cat("Ginger")
julia> meets(ginger, fido)
"hisses"
Valósítsd meg az encounter() függvényt egy metódussal, amely leírja, mi történik, amikor két elnevezett entitás találkozik.
julia> encounter(ginger, fido)
"Ginger meets Fido and hisses."
Mi történik, ha egy másik háziállattal találkoznak a sétájuk során, például egy lóval?
julia> bella = Horse("Bella")
Horse("Bella")
julia> encounter(fido, bella)
"Fido meets Bella and is cautious."
Sok más dolog is van, amivel a háziállatok találkozhatnak, és amelyek nem háziállatok: autók, emberek, növények, természeti katasztrófák, aszteroidák… Mi történik ilyenkor?
julia> colnago = Bicycle("Colnago")
Bicycle("Colnago")
julia> encounter(ginger, colnago)
"Ginger meets Colnago and runs away."
Sok más találkozás is előfordulhat a szimulációnkban. Egy autó találkozik egy kutyával, két elektron találkozik egymással és kölcsönhatásba lép…
Lehetetlen előre lefedni az összes találkozást, ezért egy általános tartalékot fogunk megvalósítani.
julia> γ1 = Photon("γ1")
Photon("γ1")
julia> γ2 = Photon("γ2")
Photon("γ2")
julia> encounter(γ1, γ2)
"γ1 meets γ2 and nothing happens."
# This is true, photons just pass through each other.
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Julia nyelvet 35 fogalom128 feladat segítségével, valódi emberi mentorálással, mindez ingyen.