Ενότητες
Ξέρουμε από πείρας ότι είναι ακατάστατο να έχουμε όλες τις συναρτήσεις μας στο ίδιο αρχείο και με το ίδιο πεδίο δράσης. Σε αυτό το μάθημα θα καλύψουμε το πως να συλλέγουμε συναρτήσεις και να ορίζουμε ένα ειδικό χάρτη γνωστό ως δομή ώστε να οργανώνουμε πιο αποτελεσματικά τον κώδικά μας.
Ενότητες
Οι ενότητες είναι ο καλύτερος τρόπος οργάνωσης συναρτήσεων σε ένα namespace. Επιπρόσθετα της συλλογής συναρτήσεων, μας επιτρέπουν να ορίζουμε ονομασμένες και ιδιωτικές συναρτήσεις τις οποίες καλύψαμε στο μάθημα συναρτήσεων
Ας δούμε ένα βασικό παράδειγμα:
defmodule Example do
def greeting(name) do
"Γεια σου #{name}."
end
end
iex> Example.greeting "Sean"
"Γειά σου Sean."
Είναι δυνατόν να ενσωματώσουμε ενότητες στην Elixir, επιτρέποντας μας να ομαδοποιήσουμε περαιτέρω την λειτουργικότητά μας:
defmodule Example.Greetings do
def morning(name) do
"Καλημέρα #{name}."
end
def evening(name) do
"Καληνύχτα #{name}."
end
end
iex> Example.Greetings.morning "Sean"
"Καλημέρα Sean."
Ορίσματα Ενοτήτων
Τα ορίσματα ενοτήτων είναι πιο συχνά ως σταθερές στην Elixir. Ας δούμε ένα απλό παράδειγμα:
defmodule Example do
@greeting "Γεια σου"
def greeting(name) do
~s(#{@greeting} #{name}.)
end
end
Είναι σημαντικό να σημειώσουμε ότι υπάρχουν κατειλημμένα ορίσματα στην Elixir. Τα τρία πιο συχνά είναι:
-
moduledoc
— Τεκμηριώνει την τρέχουσα ενότητα. -
doc
— Τεκμηρίωση συναρτήσεων και μακροεντολών. -
behaviour
— Χρήσης μιας OTP ή μιας καθορισμένης από το χρήστη συμπεριφοράς.
Δομές
Οι δομές είναι ειδικοί χάρτες με ένα ορισμένο σύνολο κλειδιών και προκαθορισμένες τιμές. Μια δομή πρέπει να είναι ορισμένη μέσα σε μια ενότητα, από την οποία παίρνει και το όνομά της. Είναι συχνό για μια δομή να είναι το μόνο πράγμα που ορίζεται σε μια ενότητα.
Για να ορίσουμε μια δομή χρησιμοποιούμε την defstruct
μαζί με μια λίστα λέξεων κλειδί από πεδία και προκαθορισμένες τιμές:
defmodule Example.User do
defstruct name: "Sean", roles: []
end
Ας δημιουργήσουμε μερικές δομές:
iex> %Example.User{}
%Example.User<name: "Sean", roles: [], ...>
iex> %Example.User{name: "Steve"}
%Example.User<name: "Steve", roles: [], ...>
iex> %Example.User{name: "Steve", roles: [:manager]}
%Example.User<name: "Steve", roles: [:manager]>
Μπορούμε να αναβαθμίσουμε μια δομή όπως θα το κάναμε σε ένα χάρτη:
iex> steve = %Example.User{name: "Steve"}
%Example.User<name: "Steve", roles: [...], ...>
iex> sean = %{steve | name: "Sean"}
%Example.User<name: "Sean", roles: [...], ...>
Σημαντικότερα, μπορούμε να αντιπαραβάλουμε δομές με χάρτες:
iex> %{name: "Sean"} = sean
%Example.User<name: "Sean", roles: [...], ...>
Από την Elixir 1.8 οι δομές περιλαμβάνουν προσαρμοσμένη προεπισκόπηση.
Για να κατανοήσουμε τι σημαίνει αυτό και πως να το χρησιμοποιήσουμε, ας προεπισκοπήσουμε το χάρτη sean
:
iex> inspect(sean)
"%Example.User<name: \"Sean\", roles: [...], ...>"
Όλα τα πεδία μας είναι παρόντα, το οποίο αρκεί για αυτό το παράδειγμα, αλλά τι θα συνέβαινε αν είχαμε ένα προστατευόμενο πεδίο που δεν θέλαμε να συμπεριλάβουμε;
Το νέο χαρακτηριστικό @derive
μας επιτρέπει να πετύχουμε ακριβώς αυτό!
Ας αναβαθμίσουμε το παράδειγμά μας ώστε η μεταβλητή roles
δεν περιλαμβάνεται πλέον στην έξοδο:
defmodule Example.User do
@derive {Inspect, only: [:name]}
defstruct name: nil, roles: []
end
Σημείωση: θα μπορούσαμε επίσης να χρησιμοποιήσουμε την εντολή @derive {Inspect, except: [:roles]}
, είναι το ίδιο πράγμα.
Με την αναβαθμισμένη ενότητά μας ας δούμε τι συμβαίνει στο iex
:
iex> sean = %Example.User{name: "Sean"}
%Example.User<name: "Sean", ...>
iex> inspect(sean)
"%Example.User<name: \"Sean\", ...>"
Οι ρόλοι δεν συμπεριλαμβάνονται πλέον στην έξοδο!
Σύνθεση
Τώρα που ξέρουμε πως να δημιουργήσουμε ενότητες και δομές ας μάθουμε πως να προσθέσουμε λειτουργικότητα σε αυτές μέσω της σύνθεσης. Η Elixir μας παρέχει μια ποικιλία διαφορετικών τρόπων για να αλληλεπιδρούμε με άλλες ενότητες.
alias
Μας επιτρέπει να δίνουμε ψευδώνυμο σε ονόματα ενοτήτων. Χρησιμοποιείται πολύ συχνά σε κώδικα Elixir:
defmodule Sayings.Greetings do
def basic(name), do: "Γεια σου, #{name}"
end
defmodule Example do
alias Sayings.Greetings
def greeting(name), do: Greetings.basic(name)
end
# Χωρίς ψευδώνυμο
defmodule Example do
def greeting(name), do: Sayings.Greetings.basic(name)
end
Αν υπάρχει σύγκρουση μεταξύ δύο ψευδωνύμων ή απλά επιθυμούμε να δώσουμε ψευδώνυμο ένα τελείως διαφορετικό όνομα, μπορούμε να χρησιμοποιήσουμε την επιλογή της :as
:
defmodule Example do
alias Sayings.Greetings, as: Hi
def print_message(name), do: Hi.basic(name)
end
Είναι επίσης εφικτό να δώσουμε ψευδώνυμο σε πολλές ενότητες μαζί:
defmodule Example do
alias Sayings.{Greetings, Farewells}
end
import
Αν θέλουμε να εισάγουμε συναρτήσεις και μακροεντολές αντί να δώσουμε ψευδώνυμο στην ενότητα τότε χρησιμοποιούμε την import
:
iex> last([1, 2, 3])
** (CompileError) iex:9: undefined function last/1
iex> import List
nil
iex> last([1, 2, 3])
3
Φιλτράρισμα
Είναι προκαθορισμένο όλες οι συναρτήσεις και οι μακροεντολές να εισάγονται αλλά μπορούμε να τις φιλτράρουμε χρησιμοποιώντας τις επιλογές :only
και :except
.
Για να εισάγουμε συγκεκριμένες συναρτήσεις και μακροεντολές, πρέπει να παρέχουμε το ζευγάρι όνομα/τάξη στις :only
και :except
.
Ας ξεκινήσουμε εισάγοντας μόνο την συνάρτηση last/1
:
iex> import List, only: [last: 1]
iex> first([1, 2, 3])
** (CompileError) iex:13: undefined function first/1
iex> last([1, 2, 3])
3
Αν εισάγουμε τα πάντα εκτός της last/1
και δοκιμάσουμε τις ίδιες συναρτήσεις με πριν:
iex> import List, except: [last: 1]
nil
iex> first([1, 2, 3])
1
iex> last([1, 2, 3])
** (CompileError) iex:3: undefined function last/1
Επιπρόσθετα στα ζεύγη όνομα/τάξη υπάρχουν δύο ειδικά άτομα, τα :functions
και :macros
, τα οποία εισάγουν μόνο συναρτήσεις και μακροεντολές αντίστοιχα:
import List, only: :functions
import List, only: :macros
require
Θα μπορούσαμε να χρησιμοποιήσουμε την require
για να πούμε στην Elixir ότι θα χρησιμοποιήσουμε macros από άλλες ενότητες.
Η μικρή διαφορά με την import
είναι ότι επιτρέπει την χρήση μακροεντολών, αλλά όχι συναρτήσεων από την οριζόμενη ενότητα:
defmodule Example do
require SuperMacros
SuperMacros.do_stuff
end
Αν προσπαθήσουμε να καλέσουμε μια μακροεντολη η οποία δεν έχει φορτωθεί ακόμα η Elixir θα σηκώσει ένα σφάλμα.
use
Με τη χρήση της use
μακροεντολής μπορούμε να επιτρέψουμε σε μια άλλη ενότητα να μετατρέψει τον ορισμό της τρέχουσας ενότητας.
Όταν καλούμε την use
στον κώδικά μας στην πραγματικότητα τρέχουμε την __using__/1
επανάκληση που ορίζεται στην παρεχόμενη ενότητα.
Το αποτέλεσμα της μακροεντολής __using__/1
γίνεται μέρος του ορισμού της ενότητάς μας.
Για να καταλάβετε καλύτερα πως λειτουργεί αυτό ας δούμε ένα απλό παράδειγμα:
defmodule Hello do
defmacro __using__(_opts) do
quote do
def hello(name), do: "Hi, #{name}"
end
end
end
Εδώ δημιουργούμε μια νέα ενότητα Hello
που ορίζει την επανάκληση __using__/1
μέσα από την οποία ορίζουμε τη συνάρτηση hello/1
.
Ας δημιουργήσουμε μια νέα ενότητα ώστε να δοκιμάσουμε το νέο μας κώδικα:
defmodule Example do
use Hello
end
Αν δοκιμάσουμε τον κώδικά μας στο IEx θα δούμε ότι η hello/1
είναι διαθέσιμη στην ενότητα Example
:
iex> Example.hello("Sean")
"Hi, Sean"
Εδώ μπορούμε να δούμε ότι η use
κάλεσε την __using__/1
επανάκληση στην ενότητα Hello
η οποία με τη σειρά της πρόσθεσε το αποτέλεσμα σαν κώδικα στην ενότητά μας.
Τώρα που δείξαμε ένα απλό παράδειγμα ας αναβαθμίσουμε τον κώδικά μας ώστε να δούμε πως η __using__/1
μπορεί να υποστηρίξει επιλογές.
Θα το κάνουμε με την προσθήκη μιας επιλογής greeting
:
defmodule Hello do
defmacro __using__(opts) do
greeting = Keyword.get(opts, :greeting, "Hi")
quote do
def hello(name), do: unquote(greeting) <> ", " <> name
end
end
end
Ας αναβαθμίσουμε την ενότητά μας Example
ώστε να περιλαμβάνει την νέα μας επιλογή greeting
:
defmodule Example do
use Hello, greeting: "Hola"
end
Αν την δοκιμάσουμε στο IEx θα δούμε ότι ο χαιρετισμός άλλαξε:
iex> Example.hello("Sean")
"Hola, Sean"
Αυτά είναι απλά παραδείγματα για να δείξουμε πως λειτουργεί η use
αλλά είναι ένα εξαιρετικά δυνατό εργαλείο στην εργαλειοθήκη της Elixir.
Καθώς θα μαθαίνετε περισσότερα για την Elixir έχετε το νού σας στην use
, ένα παράδειγμα που θα δείτε σίγουρα είναι το use ExUnit.Case, async: true
.
Σημείωση: οι quote
, alias
, use
και require
είναι μακροεντολές που χρησιμοποιούνται όταν δουλεύουμε με τον μεταπρογραμματισμό.
Έπιασες λάθος ή θέλεις να συνεισφέρεις στο μάθημα; Επεξεργαστείτε αυτό το μάθημα στο GitHub!