use használataA use makróval gyorsan kiterjeszthetjük a modulunkat egy másik modul által nyújtott funkciókkal. Amikor use-olunk egy modult, az a modul kódot szúrhat be a modulunkba: például definiálhat függvényeket, import-álhat vagy alias-olhat más modulokat, illetve beállíthat modulattribútumokat.
Ha valaha is megnézted néhány itteni Elixir-feladat tesztfájlját az Exercism-ön, valószínűleg feltűnt, hogy mindegyik use ExUnit.Case sorral kezdődik. Ez az egyetlen kódsor teszi elérhetővé a test és assert makrókat a tesztmodulban.
defmodule LasagnaTest do
use ExUnit.Case
test "expected minutes in oven" do
assert Lasagna.expected_minutes_in_oven() === 40
end
end
__using__/1 makróAz, hogy pontosan mi történik, amikor use-olsz egy modult, annak a modulnak a __using__/1 makrójától függ. Ez egy argumentumot fogad, egy kulcsszólistát a beállításokkal, és egy quoted kifejezést ad vissza. A quoted kifejezésben lévő kód a use hívásakor bekerül a modulunkba.
defmodule ExUnit.Case do
defmacro __using__(opts) do
# some real-life ExUnit code omitted here
quote do
import ExUnit.Assertions
import ExUnit.Case, only: [describe: 2, test: 1, test: 2, test: 3]
end
end
end
A beállítások második argumentumként adhatók meg a use hívásakor, például use ExUnit.Case, async: true. Ha nem adjuk meg őket kifejezetten, az alapértelmezett értékük egy üres lista.
A behaviour segítségével interfészeket (függvények és makrók halmazát) definiálhatunk egy behaviour modulban, amelyeket később különböző callback modulok valósíthatnak meg. A közös interfésznek köszönhetően ezek a callback modulok felcserélhetően használhatók.
Figyeld meg a „behaviours” brit helyesírását.
Ahhoz, hogy definiáljunk egy behaviourt, létre kell hoznunk egy új modult, és meg kell adnunk a kívánt interfészhez tartozó függvények listáját. Minden függvényt a @callback modulattribútummal kell definiálni. A szintaxis megegyezik egy függvény typespec (@spec) szintaxisával. Meg kell adnunk a függvény nevét, az argumentumtípusok listáját és az összes lehetséges visszatérési típust.
defmodule Countable do
@callback count(collection :: any) :: pos_integer
end
Ahhoz, hogy egy már meglévő behaviourt adjunk a modulunkhoz (azaz létrehozzunk egy callback modult), a @behaviour modulattribútumot használjuk. Az értéke annak a behaviour modulnak a neve legyen, amelyet hozzáadunk.
Ezután definiálnunk kell minden függvényt (callbacket), amelyet az adott behaviour modul megkövetel. Ha valaki más behaviourjét valósítjuk meg, például az Elixir beépített Access vagy GenServer behaviourjét, akkor a behaviour összes callbackjének listáját a hexdocs.pm dokumentációjában találjuk.
Egy callback modul nem korlátozódik arra, hogy csak a behaviourje részét képező függvényeket valósítsa meg. Egyetlen modul több behaviourt is megvalósíthat.
Hogy jelezzük, melyik függvény melyik behaviourből származik, minden függvény elé tegyük oda a @impl modulattribútumot. Az értéke annak a behaviour modulnak a neve legyen, amely ezt a callbacket definiálja.
defmodule BookCollection do
@behaviour Countable
defstruct [:list, :owner]
@impl Countable
def count(collection) do
Enum.count(collection.list)
end
def mark_as_read(collection, book) do
# other function unrelated to the Countable behaviour
end
end
Amikor behaviourt definiálunk, lehetőség van arra, hogy egy callbackhez alapértelmezett megvalósítást adjunk. Ezt a megvalósítást a __using__/1 makró quoted kifejezésében kell definiálni. Hogy a behaviour modul használói felülírhassák az alapértelmezett megvalósítást, a függvény megvalósítása után hívd meg a defoverridable/1 makrót. Ez egy kulcsszólistát fogad, amelyben a kulcsok a függvénynevek, az értékek pedig a függvények aritásai.
defmodule Countable do
@callback count(collection :: any) :: pos_integer
defmacro __using__(_) do
quote do
@behaviour Countable
def count(collection), do: Enum.count(collection)
defoverridable count: 1
end
end
end
Vedd figyelembe, hogy a __using__/1-en belül nem tanácsos függvényeket definiálni az alapértelmezett callback-megvalósításokon kívül más célra, de a függvényeket mindig definiálhatod egy másik modulban, és importálhatod őket a __using__/1 makróban.
A barátod, aki feltörekvő művész, egy projektötlettel keresett meg. Ötvözzük a vizuális kreativitását a te technikai szakértelmeddel. Itt az idő, hogy belemerüljünk a generatív művészetbe!
A megkötések segítik a kreativitást és lerövidítik a projektek határidejét, ezért megegyeztetek, hogy a remekműveteket egyetlen formára korlátozzátok: a körre. De sok kör lesz. És mozogni is tudnak majd! A neve pedig... táncoló pontok lesz.
A barátod biztosan új, bonyolult mozgásokat fog kitalálni a pontoknak, ezért a kódolást azzal kezded, hogy létrehozol egy architektúrát, amelynek segítségével később könnyedén definiálhatsz új animációkat.
Minden animációs modulnak két callbacket kell megvalósítania: az init/1-et és a handle_frame/3-at. Ezeket az Animation modulban definiáld.
Definiáld az init/1 callbacket. Egy opts típusú argumentumot kell fogadnia, és vagy egy {:ok, opts}, vagy egy {:error, error} tuple-t kell visszaadnia. A callback implementációi ellenőrzik majd, hogy a megadott opciók érvényesek-e ehhez az adott animációtípushoz.
Definiáld a handle_frame/3 callbacket. Három argumentumot kell fogadnia: a pontot, egy képkockaszámot és az opciókat. Mindig egy pontot kell visszaadnia. A callback implementációi az aktuális képkockaszám és az animáció opciói alapján módosítják majd a pont attribútumait.
init/1 callbackhezAz Animation viselkedést könnyű legyen beépíteni más modulokba a use DancingDots.Animation hívással.
Ehhez implementáld a __using__ makrót az Animation modulban úgy, hogy az Animation modult állítsa be a másik modul viselkedéseként. Ezenkívül adjon egy alapértelmezett implementációt az init/1 callbackhez. Az init/1 alapértelmezett implementációja változatlanul adja vissza a kapott opciókat.
defmodule MyCustomAnimation do
use DancingDots.Animation
end
MyCustomAnimation.init(some_option: true)
# => {:ok, [some_option: true]}
Flicker animációtAz Animation viselkedés használatával implementálj egy villódzó animációt.
Az alapértelmezett init/1 callbacket kell használnia, mert nem fogad semmilyen opciót.
Implementáld a handle_frame/3 callbacket, amely egyetlen képkockát kezel. Ha a képkockaszám néggyel osztható, a függvény az eredeti átlátszóság felével adja vissza a pontot. A többi képkockában változatlanul adja vissza a pontot.
A képkockákat 1-től számoljuk. A handle_frame/3-nak átadott pont mindig az eredeti állapotában lévő pont, nem az előző képkocka állapotában lévő.
dot = %DancingDots.Dot{x: 100, y: 100, radius: 24, opacity: 1}
DancingDots.Flicker.handle_frame(dot, 1, [])
# => %DancingDots.Dot{opacity: 1, radius: 24, x: 100, y: 100}
DancingDots.Flicker.handle_frame(dot, 4, [])
# => %DancingDots.Dot{opacity: 0.5, radius: 24, x: 100, y: 100}
Zoom animációtAz Animation viselkedés használatával implementálj egy zoomoló animációt.
Ez az animáció egyetlen opciót fogad: a sebességet. A sebesség bármilyen szám lehet. Ha negatív, a pont inkább kicsinyítve lesz, mint nagyítva.
Implementáld az init/1 callbacket. Ellenőriznie kell, hogy a kapott opciók egy :velocity kulcsot tartalmazó kulcsszólista-e. A sebesség értékének számnak kell lennie. Ha nem szám, add vissza a következő hibát: "The :velocity option is required, and its value must be a number. Got: #{inspect(velocity)}".
Implementáld a handle_frame/3 callbacket. A pontot megnövelt sugárral adja vissza: a sugár növekménye az aktuális képkockaszám mínusz egy, megszorozva a sebességgel.
A képkockákat 1-től számoljuk. A handle_frame/3-nak átadott pont mindig az eredeti állapotában lévő pont, nem az előző képkocka állapotában lévő.
DancingDots.Zoom.init(velocity: nil)
# => {:error, "The :velocity option is required, and its value must be a number. Got: nil"}
dot = %DancingDots.Dot{x: 100, y: 100, radius: 24, opacity: 1}
DancingDots.Zoom.handle_frame(dot, 1, velocity: 10)
# => %DancingDots.Dot{radius: 24, opacity: 1, x: 100, y: 100}
DancingDots.Zoom.handle_frame(dot, 2, velocity: 10)
# => %DancingDots.Dot{radius: 34, opacity: 1, x: 100, y: 100}
Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Elixir nyelvet 58 fogalom168 feladat segítségével, valódi emberi mentorálással, mindez ingyen.