Do you want to pick up from where you left of?
Take me there

Προδιαγραφές και τύποι

Σε αυτό το μάθημα θα μάθουμε για τα συντακτικά @spec και @type. Το πρώτο είναι συμπληρωματικό συντακτικού για να γράφουμε τεκμηρίωση που μπορεί να αναλυθεί από εργαλεία. Το δεύτερο μας βοηθάει να γράφουμε πιο ευανάγνωστο και εύκολο στην κατανόηση κώδικα.

Εισαγωγή

Είναι συνηθισμένο να θέλετε να περιγράψετε τη διεπαφή της συνάρτησής σας. Φυσικά μπορείτε να χρησιμοποιήσετε τις οδηγίες @doc, αλλά είναι μόνο πληροφορίες για άλλους προγραμματιστές που δεν ελέγχονται κατά τη ώρα της σύνταξης. Για αυτό το σκοπό η Elixir έχει τις οδηγίες @spec για να περιγράψει τις προδιαγραφές μιας συνάρτησης που θα ελεγχθεί από το συντάκτη.

Πάντως, σε μερικές περιπτώσεις οι προδιαγραφές θα είναι αρκετά μεγάλες και περίπλοκες. Αν θέλετε να μειώσετε την πολυπλοκότητα, θέλετε να εισάγετε ορισμούς ειδικών τύπων. Η Elixir έχει τις οδηγίες @type για αυτό. Κατά τα άλλα, η Elixir παραμένει δυναμική γλώσσα. Αυτό σημαίνει ότι όλες οι πληροφορίες για τον τύπο θα αγνοηθούν από το συντάκτη, αλλά θα χρησιμοποιηθούν από άλλα εργαλεία.

Προδιαγραφές

Αν έχετε εμπειρία με τη Java μπορείτε να σκεφτείτε τις προδιαγραφές σαν ένα interface. Οι προδιαγραφές ορίζουν τι τύπου θα πρέπει να είναι οι παράμετροι και η επιστρεφόμενη τιμή μιας συνάρτησης.

Για να ορίσουμε τους τύπους εισόδου και εξόδου χρησιμοποιούμε την οδηγία @spec τοποθετημένη ακριβώς πριν από τον ορισμό της συνάρτησης και δεχόμενη σαν παραμέτρους το όνομα της συνάρτησης, τη λίστα των τύπων παραμέτρων και μετά τα :: τον τύπο επιστροφής.

Ας δούμε ένα παράδειγμα:

@spec sum_product(integer) :: integer
def sum_product(a) do
  [1, 2, 3]
  |> Enum.map(fn el -> el * a end)
  |> Enum.sum()
end

Τα πάντα δείχνουν οκ και όταν την καλούμε θα επιστραφεί ένα έγκυρο αποτέλεσμα, αλλά η συνάρτηση Enum.sum επιστρέφει number, όχι integer όπως περιμένουμε στην @spec. Αυτό μπορεί να γίνει πηγή σφαλμάτων! Υπάρχουν εργαλεία όπως το Dialyzer για στατική ανάλυση του κώδικα που βοηθάει να βρούμε τέτοιου τύπου σφάλματα. Θα μιλήσουμε για αυτά σε άλλο μάθημα.

Ειδικοί τύποι

Η εγγραφή προδιαγραφών είναι ωραία, αλλά μερικές φορές οι συναρτήσεις μας δουλεύουν με πιο περίπλοκες δομές δεδομένων αντί για απλούς αριθμούς ή συλλογές. Σε αυτή την περίπτωση ορισμού του @spec θα ήταν πολύ δύσκολο να το καταλάβουμε και/ή να το αλλάξουμε για άλλους προγραμματιστές. Μερικές φορές οι συναρτήσεις χρειάζονται να δέχονται ένα μεγάλο αριθμό παραμέτρων ή να επιστρέφουν περίπλοκα δεδομένα. Μια μεγάλη λίστα πααραμέτρων είναι μια από τις πολλές ενδεχόμενες κακές οσμές στον κώδικα κάποιου. Στις αντικειμενοστραφείς γλώσσες όπως ή Ruby ή η Java μπορούμε έυκολα να ορίσουμε κλάσεις που βοηθούν να λύσουμε αυτό το πρόβλημα. Η Elixir δεν έχει κλάσεις αλλά επειδή είναι εύκολο να την επεκτείνουμε θα μπορούσαμε να ορίσουμε τους δικούς μας τύπους.

Προκαθορισμένα η Elixir περιέχει μερικούς βασικούς τύπους όπως ο integer και ο pid. Μπορείτε να βρείτε μια πλήρη λίστα των διαθέσιμων τύπων στην τεκμηρίωση.

Ορισμός ειδικών τύπων

Ας αλλάξουμε τη συνάρτησή μας sum_times και ας παρουσιάσουμε μερικές έξτρα παραμέτρους:

@spec sum_times(integer, %Examples{first: integer, last: integer}) :: integer
def sum_times(a, params) do
  for i <- params.first..params.last do
    i
  end
  |> Enum.map(fn el -> el * a end)
  |> Enum.sum()
  |> round
end

Παρουσιάσαμε μια δομή στην ενότητα Examples που περιέχει δύο πεδία, τα first και last. Αυτά είναι μια πιο απλή έκδοση της δομής από την ενότητα Range. Θα μιλήσουμε για τις structs όταν θα κουβεντιάζουμε για τις ενότητες. Ας φανταστούμε ότι πρέπει να γράψουμε προδιαγραφές για τη δομή της Examples σε πολλά μέρη. Θα ήταν πολύ ενοχλητικό να γράφουμε μεγάλες, πολύπλοκες προδιαγραφές και θα μπορούσε να ήταν πηγή σφαλμάτων. Μια λύση στο πρόβλημα θα ήταν η @type.

Η Elixir έχει τρείς οδηγίες για τύπους:

Ας ορίσουμε τον τύπο μας:

defmodule Examples do
  defstruct first: nil, last: nil

  @type t(first, last) :: %Examples{first: first, last: last}

  @type t :: %Examples{first: integer, last: integer}
end

Ορίσαμε τον τύπο t(first, last) ήδη, ο οποίος είναι μια αναπαράσταση της δομής %Examples{first: first, last: last}. Σε αυτό το σημείο βλέπουμε ότι οι τύποι μπορούν να δέχονται παραμέτρους, αλλά ορίσαμε τον τύπο t επίσης, και αυτή τη φορά είναι αναπαράσταση της δομής %Examples{first: integer, last: integer}.

Ποιά είναι η διαφορά; Η πρώτη αναπαριστά τη δομή Examples στην οποία τα δύο κλειδιά θα μπορούσαν να είναι οποιουδήποτε τύπου. Η δεύτερη αναπαριστά δομή στην οποία τα κλειδιά είναι integers. Αυτό σημαίνει ότι ο παρακάτω κώδικας:

@spec sum_times(integer, Examples.t()) :: integer
def sum_times(a, params) do
  for i <- params.first..params.last do
    i
  end
  |> Enum.map(fn el -> el * a end)
  |> Enum.sum()
  |> round
end

Είναι όμοιος με τον κώδικα:

@spec sum_times(integer, Examples.t(integer, integer)) :: integer
def sum_times(a, params) do
  for i <- params.first..params.last do
    i
  end
  |> Enum.map(fn el -> el * a end)
  |> Enum.sum()
  |> round
end

Τεκμηρίωση τύπων

Το τελευταίο στοιχείο που χρειάζεται να συζητήσουμε είναι η τεκμηρίωση των τύπων μας. Όπως ξέρουμε από το μάθημα της τεκμηρίωσης έχουμε τις οδηγίες @doc και @moduledoc για να δημιουργήσουμε τεκμηρίωση για συναρτήσεις και ενότητες. Για την τεκμηρίωση των τύπων μπορούμε να χρησιμοποιήσουμε την @typedoc:

defmodule Examples do
  @typedoc """
      Τύπος που αναπαριστά τη δομή Examples με το :first σαν ακέραιο και το :last σαν ακέραιο.
  """
  @type t :: %Examples{first: integer, last: integer}
end

Η οδηγία @typedoc είναι παρόμοια με τις @doc και @moduledoc.

Έπιασες λάθος ή θέλεις να συνεισφέρεις στο μάθημα; Επεξεργαστείτε αυτό το μάθημα στο GitHub!