From 36aeaabb66fbb3f1295e8407d86bc831fee8bd53 Mon Sep 17 00:00:00 2001 From: George Mantzouranis Date: Sat, 20 Aug 2016 07:06:19 +0300 Subject: [PATCH] greek translations: advanced (#626) * translations/advanced * greek advanced/otp-concurrency translation --- gr/lessons/advanced/concurrency.md | 168 ++++++++++++++++++ gr/lessons/advanced/erlang.md | 139 +++++++++++++++ gr/lessons/advanced/error-handling.md | 152 +++++++++++++++++ gr/lessons/advanced/escripts.md | 89 ++++++++++ gr/lessons/advanced/otp-concurrency.md | 228 +++++++++++++++++++++++++ 5 files changed, 776 insertions(+) create mode 100644 gr/lessons/advanced/concurrency.md create mode 100644 gr/lessons/advanced/erlang.md create mode 100644 gr/lessons/advanced/error-handling.md create mode 100644 gr/lessons/advanced/escripts.md create mode 100644 gr/lessons/advanced/otp-concurrency.md diff --git a/gr/lessons/advanced/concurrency.md b/gr/lessons/advanced/concurrency.md new file mode 100644 index 0000000000..8dfae950e7 --- /dev/null +++ b/gr/lessons/advanced/concurrency.md @@ -0,0 +1,168 @@ +--- +layout: page +title: Συγχρονισμός +category: advanced +order: 4 +lang: gr +--- + +Ένα από τα σημαντικά σημεία της Elixir είναι η υποστήριξή της για συγχρονισμό. Χάρη στην Εικονική Μηχανή της Erlang (BEAM), ο συγχρονισμός στην Elixir είναι πιο εύκολο από το αναμενόμενο. Το μοντέλο συγχρονισμού στηρίζεται στους Ηθοποιούς, μια περιορισμένη διεργασία που επικοινωνεί με άλλες διεργασίες μέσω αποστολής μηνυμάτων. + +Σε αυτό το μάθημα θα δούμε τις ενότητες συγχρονισμού που έρχονται με την Elixir. Στο επόμενο κεφάλαιο θα καλύψουμε τις συμπεριφορές OTP που τις υλοποιούν. + +{% include toc.html %} + +## Διεργασίες + +Οι διεργασίες στην εικονική μηχανή της Erlang είναι ελαφριές και τρέχουν σε όλους τους επεξεργαστές. Παρόλο που φαίνονται σαν τοπικά νήματα, είναι πιο απλές και είναι συνηθισμένο να έχουμε χιλιάδες συγχρονισμένες διεργασίες σε μια εφαρμογή Elixir. + +Ο πιο απλός τρόπος να δημιουγήσουμε μια νέα διεργασία είναι η `spawn`, η οποία δέχεται είτε μια ανώνυμη ή μια ονομασμένη συνάρτηση. Όταν δημιουργούμε μια νά διεργασία επιστρέφει ένα _Προσδιοριστικό Διεργασία_, ή PID για να την προσδιορίζει μοναδικά στην εφαρμογή μας. + +Για να ξεκινήσουμε θα δημιουργήσουμε μια ενότητα και θα ορίσουμε μια συνάρτηση που θα θέλαμε να τρέξουμε: + +```elixir +defmodule Example do + def add(a, b) do + IO.puts(a + b) + end +end + +iex> Example.add(2, 3) +5 +:ok +``` + +Για να τρέξουμε τη συνάρτηση ασύγχρονα θα χρησιμοποιήσουμε την `spawn/3`: + +```elixir +iex> spawn(Example, :add, [2, 3]) +5 +#PID<0.80.0> +``` + +### Αποστολή Μηνυμάτων + +Για να επικοινωνήσουν, οι διεργασίες στηρίζονται στο πέρασμα μηνυμάτων. Υπάρχουν δύο κύρια συστατικά σε αυτό: οι `send/2` και `receive`. Η συνάρτηση `send/2` μας επιτρέπει να στέλνουμε μηνύματα σε PID. Για να τα λαμβάνουμε χρησιμοποιούμε την `receive` για να αντιπαραβάλουμε μηνύματα. Αν δεν βρεθεί ταίρι η εκτέλλεση συνεχίζει απερίσπαστη. + +```elixir +defmodule Example do + def listen do + receive do + {:ok, "γεια"} -> IO.puts "Κόσμε" + end + end +end + +iex> pid = spawn(Example, :listen, []) +#PID<0.108.0> + +iex> send pid, {:ok, "γεια"} +World +{:ok, "γεια"} + +iex> send pid, :ok +:ok +``` + +### Σύνδεση Διεργασιών + +Ένα πρόβλημα την `spawn` είναι όταν πρέπει να ξέρουμε πότε μια διεργασία κρασάρει. Για αυτό πρέπει να συνδέσουμε τις διεργασίες μας με την χρήση της `spawn_link`. Δύο συνδεδεμένες διεργασίες θα λάβουν ειδοποιήσεις εξόδου η μία από την άλλη: + +```elixir +defmodule Example do + def explode, do: exit(:kaboom) +end + +iex> spawn(Example, :explode, []) +#PID<0.66.0> + +iex> spawn_link(Example, :explode, []) +** (EXIT from #PID<0.57.0>) :kaboom +``` + +Μερικές φορές δεν θέλουμε τις συνδεδεμένες διεργασίες μας να κρασάρουν την τρέχουσα. Για αυτό πρέπει να παγιδεύσουμε τις εξόδους. Όταν παγιδεύουμε τις εξόδους αυτές θα ληφθούν σαν ένα μήνυμα με μορφή τούπλας: `{:EXIT, from_pid, reason}`. + +```elixir +defmodule Example do + def explode, do: exit(:kaboom) + def run do + Process.flag(:trap_exit, true) + spawn_link(Example, :explode, []) + + receive do + {:EXIT, from_pid, reason} -> IO.puts "Λόγος εξόδου: #{reason}" + end + end +end + +iex> Example.run +Λόγος εξόδου: kaboom +:ok +``` + +### Παρακολούθηση Διεργασιών + +Τι γίνεται όταν δεν θέλουμε να συνδέσουμε δύο διεργασίες αλλά παρόλα αυτά να ενημερωνόμαστε; Για αυτό μπορούμε να χρησιμοποιήσουμε την παρακολούθηση διεργασίας με την `spawn_monitor`. Όταν παρακολουθούμε μια διεργασία λαμβάνουμε ένα μήνυμα αν η διεργασία κρασάρει χωρίς να κρασάρει η τρέχουσα διεργασία ή να παγιδεύσουμε τις εξόδους. + +```elixir +defmodule Example do + def explode, do: exit(:kaboom) + def run do + {pid, ref} = spawn_monitor(Example, :explode, []) + + receive do + {:DOWN, ref, :process, from_pid, reason} -> IO.puts "Λόγος Εξόδου: #{reason}" + end + end +end + +iex> Example.run +Exit reason: kaboom +:ok +``` + +## Πράκτορες + +Οι πράκτορες είναι μια αφαίρεση γύρω από την διατήρηση κατάστασης των διεργασιών παρασκηνίου. Μπορούμε να έχουμε πρόσβαση σε αυτές από άλλες διεργασίες μέσα από την εφαρμογή και τον κόμβο μας. Η κατάσταση του Πράκτορά μας ορίζεται σαν η τιμή επιστροφής της συνάρτησής μας: + +```elixir +iex> {:ok, agent} = Agent.start_link(fn -> [1, 2, 3] end) +{:ok, #PID<0.65.0>} + +iex> Agent.update(agent, fn (state) -> state ++ [4, 5] end) +:ok + +iex> Agent.get(agent, &(&1)) +[1, 2, 3, 4, 5] +``` + +Όταν ονομάζουμε έναν Πράκτορα μπορούμε να αναφερθούμε σε αυτόν αντί στο PID του: + +```elixir +iex> Agent.start_link(fn -> [1, 2, 3] end, name: Numbers) +{:ok, #PID<0.74.0>} + +iex> Agent.get(Numbers, &(&1)) +[1, 2, 3] +``` + +## Εργασίες + +Οι Εργασίες παρέχουν ένα τρόπο να εκτελέσουμε μια συνάρτηση στο παρασκήνιο και να λάβουμε την τιμή επιστροφής της αργότερα. Μπορούν να είναι ιδιαίτερα χρήσιμες όταν χειρίζονται ακριβές λειτουργίες χωρίς να μπλοκάρουν την εκτέλεση της εφαρμογής. + +```elixir +defmodule Example do + def double(x) do + :timer.sleep(2000) + x * 2 + end +end + +iex> task = Task.async(Example, :double, [2000]) +%Task{pid: #PID<0.111.0>, ref: #Reference<0.0.8.200>} + +# Κάντε κάτι άλλο + +iex> Task.await(task) +4000 +``` diff --git a/gr/lessons/advanced/erlang.md b/gr/lessons/advanced/erlang.md new file mode 100644 index 0000000000..d456e83634 --- /dev/null +++ b/gr/lessons/advanced/erlang.md @@ -0,0 +1,139 @@ +--- +layout: page +title: Διαλειτουργικότητα με την Erlang +category: advanced +order: 1 +lang: gr +--- + +Ένα από τα βασικά πλεονεκτήματα του να χτίζεις πάνω στην εικονική μηχανή της Erlang (BEAM) είναι η πληθώρα των υπάρχουσων βιβλιοθηκών που μας είναι διαθέσιμες. Η διαλειτουργικότητα μας επιτρέπει να αξιοποιούμε αυτές τις βιβλιοθήκες και την βασική βιβλιοθήκη της Erlang από τον κώδικά μας σε Elixir. Σε αυτό το μάθημα θα δούμε πως να έχουμε πρόσβαση σε λειτουργίες της βασικής βιβλιοθήκης και σε πακέτα τρίτων της Erlang. + +{% include toc.html %} + +## Βασική Βιβλιοθήκη + +Η εκτενής βασική βιβλιοθήκη της Erlang μπορεί να προσεγγιστεί από οποιοδήποτε κώδικα Elixir στην εφαρμογή μας. Οι ενότητες της Erlang παρουσιάζονται σαν άτομα με μικρά γράμματα όπως η `:os` και `:timer`. + +Ας χρησιμοποιήσουμε την `:timer.tc` για να χρονομετρήσουμε την εκτέλεση μιας δοθείσας συνάρτησης: + +```elixir +defmodule Example do + def timed(fun, args) do + {time, result} = :timer.tc(fun, args) + IO.puts "Χρόνος: #{time}ms" + IO.puts "Αποτέλεσμα: #{result}" + end +end + +iex> Example.timed(fn (n) -> (n * n) * n end, [100]) +Χρόνος: 8ms +Αποτέλεσμα: 1000000 +``` + +Για μια πλήρη λίστα των διαθέσιμων ενοτήτων, δείτε το [Εγχειρίδιο Αναφοράς της Erlang](http://erlang.org/doc/apps/stdlib/). + +## Πακέτα Erlang + +Σε ένα προηγούμενο μάθημα καλύψαμε το Mix και τη διαχείριση των εξαρτήσεων μας. Η προσθήκη βιβλιοθηκών της Erlang δουλεύει με τον ίδιο τρόπο. Σε περίπτωση που η βιβλιοθήκη της Erlang δεν έχει ανέβει στο [Hex](https://hex.pm), μπορείτε να αναφερθείτε στο αποθετήριο git: + +```elixir +def deps do + [{:png, github: "yuce/png"}] +end +``` + +Τώρα μπορούμε να έχουμε πρόσβαση στην βιβλιοθήκη της Erlang: + +```elixir +png = :png.create(%{:size => {30, 30}, + :mode => {:indexed, 8}, + :file => file, + :palette => palette}) +``` + +## Αξιοσημείωτες Διαφορές + +Τώρα που ξέρουμε πως να χρησιμοποιήσουμε την Erlang θα πρέπει να καλύψουμε μερικά σημαντικά σημεία που προκύπτουν από την διαλειτουργικότητα με την Erlang. + +### Άτομα + +Τα άτομα της Erlang δείχνουν σχεδόν ίδια με τα αντίστοιχα της Elixir χωρίς την άνω-κάτω τελεία (`:`). Παρουσιάζονται με αλφαριθμητικά μικρών γραμμάτων και κάτω πάυλες: + +Elixir: + +```elixir +:example +``` + +Erlang: + +```erlang +example. +``` + +### Αλφαριθμητικά + +Στην Elixir όταν συζητάμε για αλφαριθμητικά εννοούμε δυαδικά κωδικοποιημένα σε UTF-8. Στην Erlang, τα αλφαριθμητικά χρησιμοποιούν διπλά εισαγωγικά αλλά αναφέρονται σαν λίστες χαρακτήρων: + +Elixir: + +```elixir +iex> is_list('Παράδειγμα') +true +iex> is_list("Παράδειγμα") +false +iex> is_binary("Παράδειγμα") +true +iex> <<"Παράδειγμα">> === "Παράδειγμα" +true +``` + +Erlang: + +```erlang +1> is_list('Παράδειγμα'). +false +2> is_list("Παράδειγμα"). +true +3> is_binary("Παράδειγμα"). +false +4> is_binary(<<"Παράδειγμα">>). +true +``` + +Είναι σημαντικό να σημειώσουμε ότι πολλές παλαιότερες βιβλιοθήκες της Erlang μπορεί να μην υποστηρίζουν τα δυαδικά, έτσι πρέπει να μετατρέψουμε τα αλφαριθμητικά της Elixir σε λίστες χαρακτήρων. Ευτυχώς αυτό είναι εύκολο να γίνει με την συνάρτηση `to_char_list/1`: + +```elixir +iex> :string.words("Γειά σου Κόσμε") +** (FunctionClauseError) no function clause matching in :string.strip_left/2 + (stdlib) string.erl:380: :string.strip_left("Γειά σου κόσμε", 32) + (stdlib) string.erl:378: :string.strip/3 + (stdlib) string.erl:316: :string.words/2 + +iex> "Γειά σου Κόσμε" |> to_char_list |> :string.words +3 +``` + +### Μεταβλητές + +Elixir: + +```elixir +iex> x = 10 +10 + +iex> x1 = x + 10 +20 +``` + +Erlang: + +```erlang +1> X = 10. +10 + +2> X1 = X + 1. +11 +``` + +Αυτό ήταν! Η αξιοποίηση της Erlang μέσα από τις Elixir εφαρμογές μας είναι εύκολη και διπλασιάζει με αποτελεσματικό τρόπο τις βιβλιοθήκες που μας είναι διαθέσιμες. diff --git a/gr/lessons/advanced/error-handling.md b/gr/lessons/advanced/error-handling.md new file mode 100644 index 0000000000..b03c24e70c --- /dev/null +++ b/gr/lessons/advanced/error-handling.md @@ -0,0 +1,152 @@ +--- +layout: page +title: Διαχείριση Σφαλμάτων +category: advanced +order: 2 +lang: gr +--- + +Παρόλο που είναι πιο συνηθισμένη η επιστροφή της τούπλας `{:error, reason}`, η Elixir υποστηρίζει εξαιρέσεις και σε αυτό το μάθημα θα δούμε πως να χειριζόμαστε σφάλματα και τους διαφορετικούς μηχανισμούς που μας είναι διαθέσιμοι. + +Γενικά η σύμβαση στην Elixir είναι να δημιουργείτε μια συνάρτηση (`example/1`) η οποία επιστρέφει `{:ok, result}` και `{:error, reason}` και μια ξεχωριστή συνάρτηση (`example/1`) που επιστρέφει το "σκέτο" `result` ή σηκώνει ένα σφάλμα. + +Αυτό το μάθημα θα εστιάσει στην αλληλεπίδραση με την τελευταία. + +{% include toc.html %} + +## Διαχείριση Σφαλμάτων + +Πριν μπορέσουμε να διαχειριστούμε τα σφάλματα χρειάζεται να τα δημιουργήσουμε και ο πιο απλός τρόπος να το κάνουμε είναι με την `raise/1`: + +```elixir +iex> raise "Ωχ όχι!" +** (RuntimeError) Ωχ όχι! +``` + +Αν θέλουμε να ορίσουμε τον τύπο και το μήνυμα, χρειάζεται η χρήση της `raise/2`: + +```elixir +iex> raise ArgumentError, message: "Ο τύπος ορίσματος είναι άκυρος" +** (ArgumentError) Ο τύπος ορίσματος είναι άκυρος +``` + +Όταν ξέρουμε ότι ένα σφάλμα μπορεί να προκύψει, θα το χειριστούμε με τη χρήση των `try/rescue` και την αντιπαραβολή προτύπων: + +```elixir +iex> try do +...> raise "Ωχ όχι!" +...> rescue +...> e in RuntimeError -> IO.puts("Ένα σφάλμα προέκυψε: " <> e.message) +...> end +Ένα σφάλμα προέκυψε: Ωχ όχι! +:ok +``` + +Είναι δυνατό να αντιπαραβάλουμε πολλαπλά σφάλματα σε μια και μοναδική rescue: + +```elixir +try do + opts + |> Keyword.fetch!(:source_file) + |> File.read! +rescue + e in KeyError -> IO.puts "Λείπει η επιλογή :source_file" + e in File.Error -> IO.puts "Αδύνατη η ανάγνωση από το πηγαίο αρχείο" +end +``` + +## After + +Κάποιες φορές μπορεί να είναι απαραίτητο να γίνουν κάποιες ενέργειες μετά τις `try/rescue`, άσχετα με το σφάλμα. Για αυτό έχουμε την `try/after`. Αν είστε εξοικειομένοι με την Ruby, αυτή είναι όμοια με την `begin/rescue/ensure` ή την `try/catch/finally` στη Java: + +```elixir +iex> try do +...> raise "Ώχ όχι!" +...> rescue +...> e in RuntimeError -> IO.puts("Ένα σφάλμα προέκυψε: " <> e.message) +...> after +...> IO.puts "Το τέλος!" +...> end +An error occurred: Ώχ όχι! +Το τέλος! +:ok +``` + +Είναι πολύ συχνή η χρήση της με αρχεία η συνδέσεις που πρέπει να κλείσουν: + +```elixir +{:ok, file} = File.open "example.json" +try do + # Κάντε κάτι καταστροφικό +after + File.close(file) +end +``` + +## Νέα Σφάλματα + +Παρόλο που η Elixir περιλαμβάνει έναν αριθμό προκαθορισμένων τύπων λαθών όπως το `RuntimeError`, διατηρούμε τη δυνατότητα να δημιουργήσουμε τα δικά μας αν χρειαζόμαστε κάτι συγκεκριμμένο. Η δημιουργία ενός νέου σφάλματος είναι έυκολη με την χρήση της μακροεντολής `defexception/1` η οποία βολικά δέχεται την επιλογή `:message` σαν το προκαθορισμένο μήνυμα σφαλμάτων: + +```elixir +defmodule ExampleError do + defexception message: "προέκυψε ένα παραδειγματικό σφάλμα" +end +``` + +Ας δοκιμάσουμε το νέο μας σφάλμα: + +```elixir +iex> try do +...> raise ExampleError +...> rescue +...> e in ExampleError -> e +...> end +%ExampleError{message: "προέκυψε ένα παραδειγματικό σφάλμα"} +``` + +## Ρίψεις + +Ένας άλλος μηχανισμός για την εργασία με σφάλματα στην Elixir είναι οι `throw` και `catch`. Στην πράξη, αυτές προκύπτουν πολύ σπάνια σε νεότερο κώδικα Elixir αλλά είναι σημαντικό να τις ξέρουμε και να τις κατανοούμε πάραυτα. + +Η συνάρτηση `throw/1` δίνει τη δυνατότητα να βγούμε από την εκτέλεση με μια συγκεκριμμένη τιμή την οποία μπορούμε να πίασουμε (`catch`) και χρησιμοποιήσουμε: + +```elixir +iex> try do +...> for x <- 0..10 do +...> if x == 5, do: throw(x) +...> IO.puts(x) +...> end +...> catch +...> x -> "Πιάστηκε: #{x}" +...> end +0 +1 +2 +3 +4 +"Πιάστηκε: 5" +``` + +Όπως αναφέρθηκε, οι `throw/catch` είναι αρκετά σπάνιες και τυπικά υπάρχουν σαν προσωρινές λύσεις όταν βιβλιοθήκες αποτυγχάνουν να παρέχουν επαρκή API. + +## Έξοδος + +Ο τελευταίος μηχανισμός που μας παρέχει η Elixir είναι η `exit`. Τα σήματα εξόδου προκύπτουν όταν μια διεργασία πεθαίνει και είναι σημαντικό μέρος της ανοχής σφαλμάτων της Elixir. + +Για να βγούμε με σαφήνεια χρησιμοποιούμε την `exit/1`: + +```elixir +iex> spawn_link fn -> exit("oh no") end +** (EXIT from #PID<0.101.0>) "oh no" +``` + +Παρόλο που είναι πιθανόν να πιάσουμε την έξοδο με τις `try/catch`, αυτό είναι _εξαιρετικά_ σπάνιο. Σχεδόν σε όλες τις περιπτώσεις είναι επωφελές να αφήσουμε τον διαχειριστή να χειριστεί την έξοδο της διεργασίας: + +```elixir +iex> try do +...> exit "Ώχ όχι!" +...> catch +...> :exit, _ -> "μπλοκαρίστηκε η έξοδος" +...> end +"μπλοκαρίστηκε η έξοδος" +``` diff --git a/gr/lessons/advanced/escripts.md b/gr/lessons/advanced/escripts.md new file mode 100644 index 0000000000..356441af51 --- /dev/null +++ b/gr/lessons/advanced/escripts.md @@ -0,0 +1,89 @@ +--- +layout: page +title: Εκτελέσιμα +category: advanced +order: 3 +lang: gr +--- + +Για να φτιάξουμε εκτελέσιμα στην Elixir θα χρησιμοποιήσουμε την escript. Η Escript παράγει ένα εκτελέσιμο το οποίο μπορεί να τρέξει σε οποιοδήποτε σύστημα έχει εγκατασταθεί η Erlang. + +{% include toc.html %} + +## Αρχή + +Για να δημιουργήσετε ένα εκτελέσιμο με την escript υπάρχουν ελάχιστα πράγματα που πρέπει να κάνουμε: να υλοποιήσουμε μια συνάρτηση `main/1` και να αναβαθμίσουμε το Mixfile μας. + +Θα ξεκινήσουμε με τη δημιουργία μιας ενότητας που θα παίξει το ρόλο σημείου εισόδου στο εκτελέσιμό μας. Εκεί θα υλοποιήσουμε την `main/1`: + +```elixir +defmodule ExampleApp.CLI do + def main(args \\ []) do + # Κάντε τα δικά σας + end +end +``` + +Στη συνέχεια θα χρειαστεί να αναβαθμίσουμε το Mixfile μας για να προσθέσουμε την επιλογή `:escript` για το project μας μαζί με τον ορισμό του `:main_module` μας: + +```elixir +defmodule ExampleApp.Mixfile do + def project do + [app: :example_app, + version: "0.0.1", + escript: escript] + end + + def escript do + [main_module: ExampleApp.CLI] + end +end +``` + +## Επεξεργασία Ορισμάτων + +Με την εφαρμογή μας εγκατεστημένη μπορούμε να προχωρήσουμε στην επεξεργασία των ορισμάτων της γραμμής εντολών. Για να το κάνουμε αυτό θα χρησιμοποιήσουμε την συνάρτηση `OptionParser.parse/2` της Elixir με την επιλογή `:switches` για την ένδειξη ότι η σημαία μας είναι δυαδική: + +```elixir +defmodule ExampleApp.CLI do + def main(args \\ []) do + args + |> parse_args + |> response + |> IO.puts + end + + defp parse_args(args) do + {opts, word, _} = + args + |> OptionParser.parse(switches: [upcase: :boolean]) + + {opts, List.to_string(word)} + end + + defp response({opts, word}) do + if opts[:upcase], do: word = String.upcase(word) + word + end +end +``` + +## Χτίσιμο + +Όταν τελειώσουμε με την ρύθμιση της εφαρμογής μας για τη χρήση της escript, το χτίσιμο του εκτελέσιμού μας είναι πανεύκολο με το Mix: + +```elixir +$ mix escript.build +``` + +Για να το δοκιμάσουμε: + +```elixir +$ ./example_app --upcase Γεια +ΓΕΙΑ + +$ ./example_app Γεια +Γεια +``` + +Αυτό ήταν. Χτίσαμε το πρώτο μας εκτελέσιμο στην Elixir χρησιμοποιώντας την escript. diff --git a/gr/lessons/advanced/otp-concurrency.md b/gr/lessons/advanced/otp-concurrency.md new file mode 100644 index 0000000000..1794db18cb --- /dev/null +++ b/gr/lessons/advanced/otp-concurrency.md @@ -0,0 +1,228 @@ +--- +layout: page +title: Συγχρονισμός OTP +category: advanced +order: 5 +lang: gr +--- + +Είδαμε τις αφαιρέσεις της Elixir για το συγχρονισμό αλλά μερικές φορές χρειαζόμαστε μεγαλύτερο έλεγχο και για αυτό στρεφόμαστε στις συμπεριφορές OTP πάνω στις οποίες έχει χτιστεί η Elixir. + +Σε αυτό το μάθημα θα εστιάσουμε σε δύο σημαντικά κομμάτια: τους GenServers και τα GenEvents. + +{% include toc.html %} + +## GenServer + +Ένας εξυπηρετητής OTP είναι μια ενότητα με τη συμπεριφορά GenServer η οποία υλοποιεί ένα σετ επανακλήσεων. Στο πιο βασικό επίπεδό του ένας GenServer είναι ένας βρόχος που χειρίζεται μια αίτηση ανά επανάληψη και περνάει μια αναβαθμισμένη κατάσταση. + +Για να επειδίξουμε το API των GenServer θα υλοποιήσουμε μια βασική ουρά για να αποθηκεύουμε και ανακτούμε τιμές. + +Για να ξεκινήσουμε τον GenServer μας θα χρειαστεί να τον ξεκινήσουμε και να χειριστούμε την αρχικοποίηση. Στις περισσότερες περιπτώσεις θα θέλουμε να συνδέσουμε διεργασίες, έτσι θα χρησιμοποιήσουμε την `GenServer.start_link/3`. Θα περάσουμε κάποια ορίσματα και ένα σετ επιλογών GenServer στην ενότητα GenServer που ξεκινάμε. Τα ορίσματα θα περάσουν στην `GenServer.init/1` η οποία ορίζει την αρχική κατάσταση μέσω της τιμής επιστροφής της. Στο παράδειγμά μας τα ορίσματα θα είναι η αρχική μας κατάσταση: + +```elixir +defmodule SimpleQueue do + use GenServer + + @doc """ + Ξεκινάει την ουρά μας και την συνδέει. Αυτή είναι μια βοηθητική συνάρτηση + """ + def start_link(state \\ []) do + GenServer.start_link(__MODULE__, state, name: __MODULE__) + end + + @doc """ + GenServer.init/1 επανάκληση + """ + def init(state), do: {:ok, state} +end +``` + +### Σύγχρονες Συναρτήσεις + +Συχνά είναι απαραίτητο να αλληλεπιδράσουμε με τους GenServerσ με ένα σύγχρονο τρόπο, καλώντας μια συνάρτηση και περιμένοντας για την απάντησή της. Για να χειριστούμε σύγχρονες αιτήσεις θα χρειαστεί να υλοποιήσουμε την επανάκληση `GenServer.handle_call/3` η οποία δέχεται: την αίτηση, το PID της διεργασίας που καλεί, και την υπάρχουσα κατάσταση. Είναι αναμενόμενο να απαντήσει επιστρέφοντας μια τούπλα: `{:reply, response, state}`. + +Με την αντιπαραβολή προτύπων μπορούμε να ορίσουμε επανακλήσεις για πολλές διαφορετικές αιτήσεις και καταστάσεις. Μια πλήρης λίστα των αποδεκτών επιστρεφόμενων τιμών μπορεί να βρεθεί στα έγγραφα της [`GenServer.handle_call/3`](http://elixir-lang.org/docs/stable/elixir/GenServer.html#c:handle_call/3). + +Για να επιδείξουμε τις σύγχρονες αιτήσεις, ας προσθέσουμε την ικανότητα να προβάλουμε την τρέχουσα ουρά και να αφαιρέσουμε μια τιμή: + +```elixir +defmodule SimpleQueue do + use GenServer + + ### GenServer API + + @doc """ + GenServer.init/1 επανάκληση + """ + def init(state), do: {:ok, state} + + @doc """ + GenServer.handle_call/3 επανάκληση + """ + def handle_call(:dequeue, _from, [value|state]) do + {:reply, value, state} + end + def handle_call(:dequeue, _from, []), do: {:reply, nil, []} + + def handle_call(:queue, _from, state), do: {:reply, state, state} + + ### Client API / Βοηθητικές συναρτήσεις + + def start_link(state \\ []) do + GenServer.start_link(__MODULE__, state, name: __MODULE__) + end + + def queue, do: GenServer.call(__MODULE__, :queue) + def dequeue, do: GenServer.call(__MODULE__, :dequeue) +end + +``` + +Ας ξεκινήσουμε την SimpleQueue μας και ας δοκιμάσουμε την νέα μας λειτουργικότητα dequeue: + +```elixir +iex> SimpleQueue.start_link([1, 2, 3]) +{:ok, #PID<0.90.0>} +iex> SimpleQueue.dequeue +1 +iex> SimpleQueue.dequeue +2 +iex> SimpleQueue.queue +[3] +``` + +### Ασύγχρονες Συναρτήσεις + +Οι ασύγχρονες αιτήσεις χειρίζονται από την επανάκληση `handle_cast/2`. Αυτή δουλεύει σχεδόν σαν την `handle_call/3`, αλλά δεν δέχεται την διεργασία που καλεί και δεν αναμένεται να απαντήσει. + +Θα υλοποιήσουμε την λειτουργικότητά μας enqueue ώστε να είναι ασύγχρονη, ενημερώνοντας την queue αλλά χωρίς να εμποδίζουμε την τρέχουσα εκτέλεση: + +```elixir +defmodule SimpleQueue do + use GenServer + + ### GenServer API + + @doc """ + GenServer.init/1 επανάκληση + """ + def init(state), do: {:ok, state} + + @doc """ + GenServer.handle_call/3 επανάκληση + """ + def handle_call(:dequeue, _from, [value|state]) do + {:reply, value, state} + end + def handle_call(:dequeue, _from, []), do: {:reply, nil, []} + + def handle_call(:queue, _from, state), do: {:reply, state, state} + + @doc """ + GenServer.handle_cast/2 επανάκληση + """ + def handle_cast({:enqueue, value}, state) do + {:noreply, state ++ [value]} + end + + ### Client API / Βοηθητικές Συναρτήσεις + + def start_link(state \\ []) do + GenServer.start_link(__MODULE__, state, name: __MODULE__) + end + def queue, do: GenServer.call(__MODULE__, :queue) + def enqueue(value), do: GenServer.cast(__MODULE__, {:enqueue, value}) + def dequeue, do: GenServer.call(__MODULE__, :dequeue) +end +``` + +Ας χρησιμοποιήσουμε την νέα μας λειτουργικότητα: + +```elixir +iex> SimpleQueue.start_link([1, 2, 3]) +{:ok, #PID<0.100.0>} +iex> SimpleQueue.queue +[1, 2, 3] +iex> SimpleQueue.enqueue(20) +:ok +iex> SimpleQueue.queue +[1, 2, 3, 20] +``` + +Για περισσότερες πληροφορίες ελέγξτε την επίσημη τεκμηρίωση του [GenServer](http://elixir-lang.org/docs/stable/elixir/GenServer.html#content). + +## GenEvent + +Μάθαμε ότι οι Genservers είναι διεργασίες που μπορούν να διατηρούν κατάσταση και να χειρίζονται σύγχρονες και ασύγχρονες αιτήσεις. Τότε τί είναι ένα GenEvent; Τα GenEvents είναι γενικοί χειριστές συμβάντων που δέχονται εισερχόμενα συμβάντα και ειδοποιούν εγγεγραμμένους καταναλωτές. Παρέχουν ένα μηχανισμό για τη δυναμική προσθήκη και αφαίρεση χειριστών σε ροές συμβάντων. + +### Χειρισμός Συμβάντων + +Η πιο σημαντική επανάκληση στα GenEvents όπως μπορείτε να φανταστείτε είναι η `handle_event/2`. Αυτή λαμβάνει το συμβάν και την τρέχουσα κατάσταση και αναμένεται να επιστρέψει μια τούπλα: `{:ok, state}`. + +Για να επιδείξουμε την λειτουργικότητα του GenEvent ας ξεκινήσουμε δημιουργώντας δύο χειριστές, έναν να κρατάει μια καταγραφή μηνυμάτων και την άλλη να τα διατηρεί (θεωρητικά): + +```elixir +defmodule LoggerHandler do + use GenEvent + + def handle_event({:msg, msg}, messages) do + IO.puts "Καταγραφή νέου μηνύματος: #{msg}" + {:ok, [msg|messages]} + end +end + +defmodule PersistenceHandler do + use GenEvent + + def handle_event({:msg, msg}, state) do + IO.puts "Διατήρηση μηνύματος καταγραφής: #{msg}" + + # Αποθήκευση μηνύματος + + {:ok, state} + end +end +``` + +### Κλήση Χειριστών + +Επιπρόσθετα στην `handle_event/2`, τα GenEvents υποστηρίζουν την `handle_call/2` ανάμεσα σε άλλες επανακλήσεις. Με την `handle_call/2` μπορούμε να χειριζόμαστε συγκεκριμένα σύγχρονα μηνύματα με τον χειριστή μας. + +Ας αναβαθμίσουμε τον `LoggerHandler` μας να περιλαμβάνει μια συνάρτηση για την λήψη του τρέχοντος μηνύματος καταγραφής: + +```elixir +defmodule LoggerHandler do + use GenEvent + + def handle_event({:msg, msg}, messages) do + IO.puts "Καταγραφή νέου μηνύματος: #{msg}" + {:ok, [msg|messages]} + end + + def handle_call(:messages, messages) do + {:ok, Enum.reverse(messages), messages} + end +end +``` + +### Χρήση των GenEvents + +Με τους χειριστές μας έτοιμους, πρέπει να εξοικειωθούμε με μερικές συναρτήσεις του GenEvent. Οι τρεις πιο σημαντικές συναρτήσεις είναι: `add_handler/3`, `notify/2`, και `call/4`. Αυτές μας επιτρέπουν να προσθέτουμε χειριστές, να αναμεταδίδουμε νέα μηνύματα και να καλούμε συγκεκριμένες συναρτήσεις χειριστών αντίστοιχα. + +Αν τα βάλουμε όλα μαζί μπορούμε να δούμε τους χειριστές μας σε δράση: + +```elixir +iex> {:ok, pid} = GenEvent.start_link([]) +iex> GenEvent.add_handler(pid, LoggerHandler, []) +iex> GenEvent.add_handler(pid, PersistenceHandler, []) + +iex> GenEvent.notify(pid, {:msg, "Γειά σου κόσμε"}) +Καταγραφή νέου μηνύματος: Γειά σου κόσμε +Διατήρηση νέου μηνύματος: Γειά σου κόσμε + +iex> GenEvent.call(pid, LoggerHandler, :messages) +["Γειά σου κόσμε"] +``` + +Δείτε την επίσημη τεκμηρίωση των [GenEvent](http://elixir-lang.org/docs/stable/elixir/GenEvent.html#content) για μια ολοκληρωμένη λίστα επανακλήσεων και λειτουργικότητας των GenEvent.