Kurzusok
/
Elixir
Elixir
/
Feladatok
/
Táncoló pontok
Táncoló pontok

Táncoló pontok

Tanulófeladat

Bevezetés

A use használata

A 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

A __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.

Behaviours

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.

Note

Figyeld meg a „behaviours” brit helyesírását.

Behaviour definiálása

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

Behaviour megvalósítása

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

Alapértelmezett callback-megvalósítások

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.

Utasítások

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.

1. Definiáld az animáció viselkedését

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.

2. Adj egy alapértelmezett implementációt az init/1 callbackhez

Az 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]}

3. Implementáld a Flicker animációt

Az 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}

4. Implementáld a Zoom animációt

Az 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}
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
Elixir Exercism

Készen állsz elkezdeni a(z) Táncoló pontok feladatot?

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.