Διαδρομές
/
Elixir
Elixir
/
Ασκήσεις
/
Χορεύουσες κουκκίδες
Χορεύουσες κουκκίδες

Χορεύουσες κουκκίδες

Άσκηση εκμάθησης

Εισαγωγή

Χρήση

Το macro use μάς επιτρέπει να επεκτείνουμε γρήγορα το module μας με λειτουργικότητα που παρέχει ένα άλλο module. Όταν κάνουμε use ένα module, αυτό το module μπορεί να εισάγει κώδικα στο module μας: μπορεί για παράδειγμα να ορίσει συναρτήσεις, να κάνει import ή alias άλλα modules, ή να ορίσει χαρακτηριστικά του module.

Αν έχεις κοιτάξει ποτέ τα αρχεία test κάποιων από τις ασκήσεις Elixir εδώ στο Exercism, πιθανότατα πρόσεξες ότι όλα ξεκινούν με use ExUnit.Case. Αυτή η μία γραμμή κώδικα είναι που κάνει τα macros test και assert διαθέσιμα στο module test.

defmodule LasagnaTest do
  use ExUnit.Case

  test "expected minutes in oven" do
    assert Lasagna.expected_minutes_in_oven() === 40
  end
end

Το macro __using__/1

Το τι ακριβώς συμβαίνει όταν κάνεις use ένα module το υπαγορεύει το macro __using__/1 αυτού του module. Παίρνει ένα όρισμα, μια λίστα λέξεων-κλειδιών με επιλογές, και επιστρέφει μια παρατιθέμενη έκφραση. Ο κώδικας σε αυτήν την παρατιθέμενη έκφραση εισάγεται στο module μας όταν καλούμε το use.

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

Οι επιλογές μπορούν να δοθούν ως δεύτερο όρισμα όταν καλείς το use, π.χ. use ExUnit.Case, async: true. Όταν δεν δίνονται ρητά, προεπιλέγονται σε μια κενή λίστα.

Συμπεριφορές

Οι συμπεριφορές μάς επιτρέπουν να ορίσουμε διεπαφές (σύνολα συναρτήσεων και macros) σε ένα module συμπεριφοράς, τις οποίες μπορούν αργότερα να υλοποιήσουν διαφορετικά modules callback. Χάρη στην κοινή διεπαφή, αυτά τα modules callback μπορούν να χρησιμοποιούνται εναλλακτικά.

Note

Πρόσεξε τη βρετανική γραφή του "behaviours".

Ορισμός συμπεριφορών

Για να ορίσουμε μια συμπεριφορά, χρειάζεται να δημιουργήσουμε ένα νέο module και να προσδιορίσουμε μια λίστα συναρτήσεων που αποτελούν μέρος της επιθυμητής διεπαφής. Κάθε συνάρτηση πρέπει να ορίζεται με το χαρακτηριστικό module @callback. Η σύνταξη είναι ίδια με ένα typespec συνάρτησης (@spec). Χρειάζεται να προσδιορίσουμε ένα όνομα συνάρτησης, μια λίστα με τους τύπους των ορισμάτων και όλους τους πιθανούς τύπους επιστροφής.

defmodule Countable do
  @callback count(collection :: any) :: pos_integer
end

Υλοποίηση συμπεριφορών

Για να προσθέσουμε μια υπάρχουσα συμπεριφορά στο module μας (να δημιουργήσουμε ένα module callback) χρησιμοποιούμε το χαρακτηριστικό module @behaviour. Η τιμή του πρέπει να είναι το όνομα του module συμπεριφοράς που προσθέτουμε.

Έπειτα, χρειάζεται να ορίσουμε όλες τις συναρτήσεις (callbacks) που απαιτεί αυτό το module συμπεριφοράς. Αν υλοποιούμε τη συμπεριφορά κάποιου άλλου, όπως τις ενσωματωμένες συμπεριφορές Access ή GenServer της Elixir, θα βρούμε τη λίστα όλων των callbacks της συμπεριφοράς στην τεκμηρίωση στο hexdocs.pm.

Ένα module callback δεν περιορίζεται στην υλοποίηση μόνο των συναρτήσεων που αποτελούν μέρος της συμπεριφοράς του. Είναι επίσης δυνατό ένα μόνο module να υλοποιεί πολλαπλές συμπεριφορές.

Για να δηλώσουμε ποια συνάρτηση προέρχεται από ποια συμπεριφορά, καλό είναι να χρησιμοποιούμε το χαρακτηριστικό module @impl πριν από κάθε συνάρτηση. Η τιμή του πρέπει να είναι το όνομα του module συμπεριφοράς που ορίζει αυτό το callback.

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

Προεπιλεγμένες υλοποιήσεις callbacks

Όταν ορίζεις μια συμπεριφορά, είναι δυνατό να παρέχεις μια προεπιλεγμένη υλοποίηση ενός callback. Αυτή η υλοποίηση πρέπει να ορίζεται μέσα στην παρατιθέμενη έκφραση του macro __using__/1. Για να μπορούν όσοι χρησιμοποιούν το module συμπεριφοράς να παρακάμπτουν την προεπιλεγμένη υλοποίηση, κάλεσε το macro defoverridable/1 μετά την υλοποίηση της συνάρτησης. Δέχεται μια λίστα λέξεων-κλειδιών με ονόματα συναρτήσεων ως κλειδιά και πλήθη ορισμάτων συναρτήσεων ως τιμές.

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

Σημείωσε ότι ο ορισμός συναρτήσεων μέσα στο __using__/1 δεν συνιστάται για οποιονδήποτε άλλο σκοπό εκτός από τον ορισμό προεπιλεγμένων υλοποιήσεων callbacks, αλλά μπορείς πάντα να ορίσεις συναρτήσεις σε ένα άλλο module και να τις εισάγεις με import στο macro __using__/1.

Οδηγίες

Ο φίλος σου, ένας επίδοξος καλλιτέχνης, επικοινώνησε μαζί σου με μια ιδέα για ένα πρότζεκτ. Ας συνδυάσουμε τη δημιουργικότητά του στα εικαστικά με την τεχνική σου εμπειρία. Ήρθε η ώρα να ασχοληθείς με τη γενετική τέχνη!

Οι περιορισμοί βοηθούν τη δημιουργικότητα και συντομεύουν τις προθεσμίες του πρότζεκτ, οπότε συμφωνήσατε και οι δύο να περιορίσετε το αριστούργημά σας σε ένα μόνο σχήμα: τον κύκλο. Αλλά θα υπάρχουν πολλοί κύκλοι. Και μπορούν να κινούνται! Θα το ονομάσεις... dancing dots.

Ο φίλος σου σίγουρα θα θέλει να επινοεί νέες περίτεχνες κινήσεις για τις κουκκίδες, οπότε θα ξεκινήσεις τον προγραμματισμό δημιουργώντας μια αρχιτεκτονική που θα σου επιτρέψει αργότερα να ορίζεις εύκολα νέα animation.

1. Όρισε τη συμπεριφορά του animation

Κάθε module animation πρέπει να υλοποιεί δύο callbacks: init/1 και handle_frame/3. Όρισέ τα στο module Animation.

Όρισε το callback init/1. Θα πρέπει να δέχεται ένα όρισμα τύπου opts και να επιστρέφει είτε μια πλειάδα {:ok, opts} είτε μια πλειάδα {:error, error}. Οι υλοποιήσεις αυτού του callback θα ελέγχουν αν οι δοσμένες επιλογές είναι έγκυρες για τον συγκεκριμένο τύπο animation.

Όρισε τα callbacks handle_frame/3. Θα πρέπει να δέχονται τρία ορίσματα: την κουκκίδα, έναν αριθμό καρέ και τις επιλογές. Θα πρέπει πάντα να επιστρέφουν μια κουκκίδα. Οι υλοποιήσεις αυτού του callback θα τροποποιούν τα χαρακτηριστικά της κουκκίδας με βάση τον τρέχοντα αριθμό καρέ και τις επιλογές του animation.

2. Δώσε μια προεπιλεγμένη υλοποίηση του callback init/1

Η συμπεριφορά Animation θα πρέπει να ενσωματώνεται εύκολα σε άλλα modules καλώντας το use DancingDots.Animation.

Για να γίνει αυτό, υλοποίησε το macro __using__ στο module Animation, ώστε να ορίζει το module Animation ως συμπεριφορά του άλλου module. Θα πρέπει επίσης να παρέχει μια προεπιλεγμένη υλοποίηση του callback init/1. Η προεπιλεγμένη υλοποίηση του init/1 θα πρέπει να επιστρέφει τις δοσμένες επιλογές χωρίς αλλαγές.

defmodule MyCustomAnimation do
  use DancingDots.Animation
end

MyCustomAnimation.init(some_option: true)
# => {:ok, [some_option: true]}

3. Υλοποίησε το animation Flicker

Χρησιμοποίησε τη συμπεριφορά Animation για να υλοποιήσεις ένα animation που τρεμοπαίζει.

Θα πρέπει να χρησιμοποιεί το προεπιλεγμένο callback init/1, επειδή δεν δέχεται επιλογές.

Υλοποίησε το callback handle_frame/3, το οποίο χειρίζεται ένα μόνο καρέ. Αν ο αριθμός καρέ είναι πολλαπλάσιο του τέσσερα, η συνάρτηση θα πρέπει να επιστρέφει την κουκκίδα με τη μισή αρχική της αδιαφάνεια. Στα υπόλοιπα καρέ, θα πρέπει να επιστρέφει την κουκκίδα χωρίς αλλαγές.

Τα καρέ μετρώνται από το 1. Η κουκκίδα που περνά στο handle_frame/3 είναι πάντα η κουκκίδα στην αρχική της κατάσταση, όχι στην κατάσταση από το προηγούμενο καρέ.

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. Υλοποίησε το animation Zoom

Χρησιμοποίησε τη συμπεριφορά Animation για να υλοποιήσεις ένα animation που κάνει ζουμ.

Αυτό το animation δέχεται μία επιλογή: την ταχύτητα. Η ταχύτητα μπορεί να είναι οποιοσδήποτε αριθμός. Αν είναι αρνητική, η κουκκίδα μικραίνει αντί να μεγαλώνει.

Υλοποίησε το callback init/1. Θα πρέπει να επαληθεύει ότι οι δοσμένες επιλογές είναι μια λίστα keyword με κλειδί :velocity. Η τιμή της ταχύτητας πρέπει να είναι αριθμός. Αν δεν είναι αριθμός, επίστρεψε το σφάλμα "The :velocity option is required, and its value must be a number. Got: #{inspect(velocity)}".

Υλοποίησε το callback handle_frame/3. Θα πρέπει να επιστρέφει την κουκκίδα με την ακτίνα της αυξημένη κατά τον τρέχοντα αριθμό καρέ μείον ένα, επί την ταχύτητα.

Τα καρέ μετρώνται από το 1. Η κουκκίδα που περνά στο handle_frame/3 είναι πάντα η κουκκίδα στην αρχική της κατάσταση, όχι στην κατάσταση από το προηγούμενο καρέ.

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}
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
Elixir Exercism

Έτοιμος να ξεκινήσεις την άσκηση Χορεύουσες κουκκίδες;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις Elixir με 58 έννοιες168 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.