Προδιαγραφές και τύποι
Σε αυτό το μάθημα θα μάθουμε για τα συντακτικά @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 έχει τρείς οδηγίες για τύπους:
-
@type
– απλός, δημόσιος τύπος. Η εσωτερική δομή του τύπου είναι δημόσια -
@typep
– ο τύπος είναι ιδιωτικός και θα μπορούσε να χρησιμοποιηθεί μόνο στην ενότητα που ορίζεται. -
@opaque
– ο τύπος είναι δημόσιος, αλλά η εσωτερική δομή είναι ιδιωτική.
Ας ορίσουμε τον τύπο μας:
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!