Kurzusok
/
Julia
Julia
/
Feladatok
/
Találkozások
Találkozások

Találkozások

Tanulófeladat

Bevezetés

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

Mi az a függvény?

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.

Hogyan kezelik a függvények az argumentumok eltérő számát és típusát?

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

  • A függvények „generikusak”, ami azt jelenti, hogy több metódus is kapcsolódhat hozzájuk.
  • A függvények csupán nevek, amíg metódusok nem kapcsolódnak hozzájuk.
  • A metódusok szükség szerint hozzáadhatók, akár nagy számban is (a + operátor jelenleg 161 metódussal rendelkező függvény).
  • Amikor egy függvényt meghívunk, a Julia megvizsgálja a hívásszignatúrát: az argumentumok számát és mindegyik aktuális típusát.
  • A hívásszignatúrához legjobban illeszkedő metódus kerül kiválasztásra.
    • Általában az argumentumok számának pontosan egyeznie kell (bár a splat/slurp operátor, a ..., ezt részben megkerülheti).
    • Azok a metódusok élveznek elsőbbséget, amelyeknek a típusai pontosan megegyeznek a hívásszignatúrával.
    • Ha szükséges, a Julia a hívásszignatúra minden argumentumának szupertípusát is megvizsgálja, hogy megtalálja a legjobban illeszkedő, legkonkrétabb metódust.
  • A kiválasztott metódus kerül dispatched-re a megadott argumentumokkal, és egy eredményt ad vissza.
  • Ha nem található megfelelő metódus, a Julia 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.

Többszörös diszpécselés: egy példa

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

Parametrikus metódusok

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)

Összegzés

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!

Utasítások

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.

1. Egyedi típusok definiálása

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.

2. A háziállat nevének lekérdezése.

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"

3. Definiáld, mi történik, amikor macskák és kutyák találkoznak

Valósítsd meg a meets() metódusokat a következő találkozásokhoz:

  • Amikor két kutya találkozik, sniff egymást.
  • Amikor egy macska találkozik egy kutyával, hisses a kutyára.
  • Amikor egy kutya találkozik egy macskával, chases a macskát.
  • Amikor két macska találkozik, slink.
julia> ginger = Cat("Ginger")
Cat("Ginger")

julia> meets(ginger, fido)
"hisses"

4. Definiálj egy találkozást két entitás között.

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

5. Definiálj egy tartalék reakciót a háziállatok közötti találkozásokhoz

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?

  • Ha egy háziállat egy másik, általa nem ismert háziállattal találkozik, is cautious.
julia> bella = Horse("Bella")
Horse("Bella")

julia> encounter(fido, bella)
"Fido meets Bella and is cautious."

6. Definiálj egy tartalékot, ha egy háziállat valami ismeretlennel találkozik

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?

  • Ha egy háziállat valamivel találkozik, amit még soha nem látott, runs away.
julia> colnago = Bicycle("Colnago")
Bicycle("Colnago")

julia> encounter(ginger, colnago)
"Ginger meets Colnago and runs away."

7. Definiálj egy általános tartalékot

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.

  • Ha két ismeretlen dolog vagy lény találkozik egymással, nothing happens.
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.
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
Julia Exercism

Készen állsz elkezdeni a(z) Találkozások feladatot?

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.