Fork me on GitHub

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

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

Πίνακας περιεχομένων

Εισαγωγή

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

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

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

Αν έχετε εμπειρία με τη Java ή τη Ruby, μπορείτε να σκεφτείτε τις προδιαγραφές σαν ένα 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.


Share This Page