Διαδρομές
/
Crystal
Crystal
/
Ύλη
/
Τύπος ένωσης
Τύ

Τύπος ένωσης σε Crystal

1 άσκηση

Σχετικά με την έννοια Τύπος ένωσης

Η Crystal επιτρέπει σε μια μεταβλητή να αποτελείται από πολλούς τύπους. Αυτό ονομάζεται τύπος ένωσης. Στην Crystal, είναι συνηθισμένο ένας τύπος ένωσης να συνάγεται από τον μεταγλωττιστή.

Note

Ένας τύπος ένωσης είναι ακόμα ένας μεμονωμένος τύπος κατά την εκτέλεση, ακόμα κι αν αποτελείται από πολλούς τύπους. Αυτό σημαίνει ότι αν ένας τύπος ένωσης αποτελείται από String και Int32, δεν θα είναι και τα δύο ταυτόχρονα. Αντ' αυτού, θα είναι είτε String είτε Int32.

Ένας τύπος ένωσης δηλώνεται χωρίζοντας τους τύπους με μια κάθετη γραμμή (|). Συχνά περικλείονται σε παρενθέσεις, αλλά αυτό δεν είναι υποχρεωτικό. Ο πιο συνηθισμένος τύπος ένωσης είναι ο (T | Nil), όπου T είναι ένας τύπος, ο οποίος μπορεί να εμφανιστεί σε μεθόδους που μπορούν να επιστρέψουν Nil. Αυτό είναι επίσης γνωστό ως μια μεταβλητή που είναι nilable.

foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)

foo : (String | Nil) = "Hello"
foo = nil

Δεν περιορίζεται σε δύο μόνο τύπους, αλλά μπορεί να έχει όσους θέλεις.

foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0

typeof έναντι Object#class

Υπάρχουν δύο τρόποι για να πάρεις τον τύπο μιας μεταβλητής. Είτε χρησιμοποιώντας το typeof είτε χρησιμοποιώντας το Object#class. Η διαφορά είναι ότι το typeof επιστρέφει τον τύπο μιας μεταβλητής κατά τη μεταγλώττιση, ενώ το Object#class επιστρέφει τον τύπο κατά την εκτέλεση. Αυτό σημαίνει ότι αν, για παράδειγμα, θέλεις να δεις αν μια μεταβλητή είναι τύπος ένωσης, το Object#class δεν θα μπορεί να σου το πει, καθώς θα επιστρέψει μόνο τον τύπο κατά την εκτέλεση, ο οποίος είναι ένας μεμονωμένος τύπος.

foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String

Πράξεις σε τύπους ένωσης

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

foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)

Η Crystal έχει όντως μια ειδική μέθοδο για τύπους ένωσης: τη μέθοδο is_a?, η οποία παίρνει έναν τύπο ως όρισμα και επιστρέφει μια τιμή Boolean. Η μέθοδος nil? είναι συντομογραφία του is_a?(Nil). Αν βάλεις τη μέθοδο is_a? σε μια έκφραση ελέγχου, θα πεις στον μεταγλωττιστή ποιος τύπος είναι και έτσι θα εγγυηθείς ότι είναι αυτός ο τύπος. Και για έναν κλάδο else, θα είναι εγγυημένο ότι δεν είναι αυτός ο τύπος.

foo : (String | Int32) = "Hello"
if foo.is_a?(String)
  typeof(foo)  # => String
  foo.downcase # => "hello"
end

Αυτό το is_a? δεν περιορίζεται στο να έχει έναν μόνο τύπο ως όρισμα, αλλά μπορεί να έχει και τύπο ένωσης. Μπορεί επίσης να συνδυαστεί με && για πολλούς τύπους.

Note

Η μέθοδος is_a?, όταν τη χρησιμοποιείς σε συνδυασμό με μια έκφραση ελέγχου, δεν μπορεί να πάρει ως όρισμα μεταβλητή στιγμιοτύπου ή μεταβλητή κλάσης. Αντ' αυτού, αυτές πρέπει πρώτα να ανατεθούν σε μια τοπική μεταβλητή.

as

Ένας τρόπος για να μετατρέψεις έναν τύπο ένωσης σε έναν μεμονωμένο τύπο είναι να κάνεις έτσι ώστε ένας κλάδος να μπορεί να εκτελεστεί μόνο αν ο τύπος είναι συγκεκριμένος. Μια άλλη προσέγγιση είναι να χρησιμοποιήσεις τη μέθοδο as. Αυτή μετατρέπει έναν τύπο ένωσης σε έναν μεμονωμένο τύπο κάνοντας έναν έλεγχο κατά την εκτέλεση. Θα προκληθεί εξαίρεση αν ο τύπος δεν είναι ο αναμενόμενος.

foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"

foo.as(Int32) # Error: can't cast String to Int32
Caution

Αυτή η προσέγγιση προορίζεται μόνο για όταν είσαι σίγουρος ότι ο τύπος είναι ο αναμενόμενος ή για όταν θέλεις να προκληθεί εξαίρεση αν δεν είναι.

Η χρήση αυτής της προσέγγισης με λανθασμένη προετοιμασία μπορεί να οδηγήσει σε απροσδόκητη συμπεριφορά.

as?

Το as? λειτουργεί πολύ παρόμοια με το as, αλλά θα επιστρέψει nil αντί να προκαλέσει εξαίρεση αν ο τύπος δεν είναι ο αναμενόμενος. Αυτό σημαίνει ότι θα επιστρέψει έναν τύπο ένωσης του αναμενόμενου τύπου και του Nil.

foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"

foo.as?(Int32) # => nil

Συντομογραφία για nilable

Nilable σημαίνει ότι μια μεταβλητή μπορεί να είναι είτε ένας τύπος είτε Nil. Αυτό μπορεί να γραφτεί ως (T | Nil). Αλλά επειδή οι nilable τύποι είναι σχετικά συνηθισμένοι, υπάρχει μια συντομογραφία γι' αυτό: T?.

# This:
foo : (String | Nil) = "Hello"
foo = nil

# Is the same as:
foo : String? = "Hello"
foo = nil

Συναγωγή τύπων ένωσης

Ο μεταγλωττιστής θα συνάγει έναν τύπο ένωσης αν δεν είναι σίγουρος ποιος τύπος είναι. Για παράδειγμα, στον παρακάτω κώδικα ο μεταγλωττιστής δεν θα ξέρει ποιος τύπος είναι το foo, αφού μπορεί να είναι είτε String είτε Int32.

if true
  foo = 1
else
  foo = "Hello"
end

typeof(foo) # => (Int32 | String)

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

character = "Hello world"[10]?
typeof(character) # => (Char | Nil)
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα

Μάθε την έννοια Τύπος ένωσης

Η εξάσκηση είναι κλειδωμένη

Ξεκλείδωσε 1 ακόμη άσκηση για να εξασκηθείς στην έννοια Τύπος ένωσης