Η Crystal επιτρέπει σε μια μεταβλητή να αποτελείται από πολλούς τύπους. Αυτό ονομάζεται τύπος ένωσης. Στη Crystal, είναι συνηθισμένο ένας τύπος ένωσης να συμπεραίνεται από τον μεταγλωττιστή.
Ένας τύπος ένωσης παραμένει ένας μόνο τύπος κατά τον χρόνο εκτέλεσης, ακόμα κι αν αποτελείται από πολλούς τύπους.
Αυτό σημαίνει ότι αν ένας τύπος ένωσης αποτελείται από 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? δεν περιορίζεται στο να έχει έναν μόνο τύπο ως όρισμα, αλλά μπορεί να δεχτεί και τύπο ένωσης.
Μπορεί επίσης να συνδυαστεί με && για πολλούς τύπους.
Όταν χρησιμοποιείς τη μέθοδο is_a? σε συνδυασμό με μια έκφραση ελέγχου, δεν μπορεί να είναι μεταβλητή στιγμιοτύπου ή μεταβλητή κλάσης.
Αντ' αυτού, πρέπει να ανατεθούν πρώτα σε μια τοπική μεταβλητή.
Ένας τρόπος για να μετατρέψεις έναν τύπο ένωσης σε έναν μόνο τύπο είναι να φροντίσεις ώστε μια διακλάδωση να μπορεί να εκτελεστεί μόνο αν ο τύπος είναι κάποιος συγκεκριμένος τύπος.
Μια άλλη προσέγγιση είναι να χρησιμοποιήσεις τη μέθοδο as.
Αυτή μετατρέπει έναν τύπο ένωσης σε έναν μόνο τύπο κάνοντας έναν έλεγχο κατά τον χρόνο εκτέλεσης.
Αν ο τύπος δεν είναι ο αναμενόμενος, θα πεταχτεί μια εξαίρεση.
foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"
foo.as(Int32) # Error: can't cast String to Int32
Αυτή η προσέγγιση προορίζεται μόνο για όταν είσαι σίγουρος ότι ο τύπος είναι ο αναμενόμενος ή για όταν θέλεις να πετάξεις μια εξαίρεση όταν δεν είναι.
Η χρήση αυτής της προσέγγισης με ακατάλληλη ρύθμιση μπορεί να οδηγήσει σε μη αναμενόμενη συμπεριφορά.
Το as? λειτουργεί με πολύ παρόμοιο τρόπο με το as, αλλά θα επιστρέψει nil αντί να πετάξει εξαίρεση, αν ο τύπος δεν είναι ο αναμενόμενος.
Αυτό σημαίνει ότι θα επιστρέψει έναν τύπο ένωσης που αποτελείται από τον αναμενόμενο τύπο και το Nil.
foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"
foo.as?(Int32) # => nil
Nilable σημαίνει ότι μια μεταβλητή μπορεί να είναι είτε ένας τύπος είτε Nil.
Αυτό μπορεί να γραφτεί ως (T | Nil).
Αλλά επειδή οι nilable τύποι είναι σχετικά συνηθισμένοι, υπάρχει μια συντομογραφία γι' αυτό: T?.
# This:
foo : (String | Nil) = "Hello"
foo = nil
# Is the same as:
foo : String? = "Hello"
foo = nil
Η εταιρεία στην οποία εργάζεσαι είναι έτοιμη να κυκλοφορήσει το ολοκαίνουργιο smartphone της, με το όνομα Smarty 5. Διαθέτει ένα ολοκαίνουργιο σύστημα κάμερας, έναν νέο σχεδιασμό και έναν επαναστατικό νέο επεξεργαστή που τρέχει σε ένα νέο λειτουργικό σύστημα, το smartyOS.
Ο νέος επεξεργαστής έχει τη δύναμη να χειρίζεται πιο ασφαλείς κωδικούς πρόσβασης από ποτέ, και η εταιρεία αποφάσισε να το εκμεταλλευτεί αυτό προς όφελός της. Σου ζήτησε να υλοποιήσεις ένα σύστημα κλειδώματος με κωδικό πρόσβασης για το τηλέφωνο, που θα επιτρέπει στον χρήστη να ορίζει έναν κωδικό πρόσβασης και μετά να ελέγχει αν ένας δεδομένος κωδικός είναι σωστός.
Το σύστημα κωδικών πρόσβασης που εξασκείς σε αυτή την άσκηση δεν είναι ασφαλές και χρησιμοποιείται μόνο για εκπαιδευτικούς σκοπούς. ΔΕΝ θα πρέπει να χρησιμοποιηθεί σε καμία πραγματική εφαρμογή.
Το τηλέφωνο επιτρέπει στους χρήστες να χρησιμοποιούν πολλούς διαφορετικούς τύπους κωδικών πρόσβασης, από έναν απλό κωδικό με ψηφία μέχρι έναν πιο ασφαλή αλφαριθμητικό κωδικό και ακόμη και έναν κωδικό που αποθηκεύει το αποτύπωμά τους.
Αυτοί οι διαφορετικοί τύποι κωδικών χρησιμοποιούν διαφορετικούς τύπους: ο κωδικός με ψηφία χρησιμοποιεί ένα Int32, ο αλφαριθμητικός κωδικός χρησιμοποιεί ένα String και ο κωδικός αποτυπώματος χρησιμοποιεί ένα Float64.
Υλοποίησε την κλάση PasswordLock με έναν αρχικοποιητή που δέχεται οποιονδήποτε από τους τρεις τύπους κωδικών ως όρισμα και τον αποθηκεύει σε μια μεταβλητή στιγμιοτύπου με το όνομα @password.
password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>
Η εταιρεία αποφάσισε να κρυπτογραφήσει τον κωδικό πρόσβασης, ώστε να μην αποθηκεύεται σε απλό κείμενο, και σου ζήτησε να υλοποιήσεις μια μέθοδο για την κρυπτογράφησή του.
Κάθε τύπος κωδικού έχει τον δικό του μοναδικό τρόπο κρυπτογράφησης:
Int32: Ο κωδικός διαιρείται με το δύο και στρογγυλοποιείται στον πλησιέστερο ακέραιο.String: Ο κωδικός αντιστρέφεται.Float64: Ο κωδικός πολλαπλασιάζεται επί τέσσερα.Υλοποίησε μια μέθοδο στιγμιοτύπου με το όνομα encrypt που δεν δέχεται ορίσματα και τροποποιεί τη μεταβλητή στιγμιοτύπου @password, ώστε να είναι κρυπτογραφημένη.
password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>
password_lock.encrypt
# => #<PasswordLock:0x7f8e1b8c0b80 @password=617>
Η εταιρεία σου ζήτησε επίσης να υλοποιήσεις μια μέθοδο που ελέγχει αν ένας δεδομένος κωδικός πρόσβασης είναι σωστός.
Θέλουν η μέθοδος να επιστρέφει nil αν ο κωδικός είναι λανθασμένος και "Unlocked" αν ο κωδικός είναι σωστός.
Υλοποίησε μια μέθοδο στιγμιοτύπου με το όνομα unlock? που δέχεται ως όρισμα τον κωδικό προς έλεγχο.
Η μέθοδος θα πρέπει να επιστρέφει nil αν ο κωδικός είναι λανθασμένος και "Unlocked" αν ο κωδικός είναι σωστός.
password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
Γράψου στο Exercism για να μάθεις και να κατακτήσεις Crystal με 26 έννοιες142 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.