Ένα μπλοκ είναι ένα κομμάτι κώδικα που μπορεί να περαστεί σε μια μέθοδο. Σε άλλες γλώσσες προγραμματισμού αυτό είναι γνωστό ως ανώνυμη συνάρτηση ή lambda. Μια μέθοδος μπορεί να μετατρέψει ένα μπλοκ σε Proc, που αντιπροσωπεύει ένα μπλοκ κώδικα.
def my_method(&block)
1 + yield
end
my_method { 2 }
# => 3
Τα μπλοκ και τα Proc χρησιμοποιούνται πολύ συχνά στη Crystal, για πράγματα όπως επανάληψη και callbacks. Αλλά και για τη δημιουργία DSL (γλώσσες ειδικού σκοπού), μερικά παραδείγματα DSL είναι το web framework Kemal ή το framework δοκιμών Spec (το οποίο χρησιμοποιείται για να ελέγξεις τις λύσεις σου σε Crystal).
Για να δημιουργήσεις μια μέθοδο που μπορεί να δεχτεί ένα μπλοκ, πρέπει να βάλεις ένα ampersand(&) πριν από το τελευταίο όρισμα.
Αυτό θα μετατρέψει το μπλοκ σε Proc και θα το εκχωρήσει στο όρισμα.
Έπειτα, με το yield εκτελείς το μπλοκ που έχει δοθεί στη μέθοδο.
def my_method(&block)
1 + yield
end
Για να περάσεις ένα μπλοκ υπάρχουν δύο τρόποι: είτε με τις λέξεις-κλειδιά do ... end είτε με άγκιστρα({}).
Η κύρια διαφορά μεταξύ τους είναι η προτεραιότητα: το do ... end υποστηρίζει μπλοκ πολλών γραμμών, ενώ τα άγκιστρα({}) όχι.
my_method do
2
end
# => 3
my_method { 2 }
# => 3
Το yield είναι μια λέξη-κλειδί που εκτελεί το μπλοκ που δόθηκε στη μέθοδο.
Αυτό σημαίνει ότι το μπλοκ δεν θα εκτελεστεί ποτέ αν δεν κληθεί το yield.
Μπορεί επίσης να δεχτεί ορίσματα, τα οποία θα περαστούν στο μπλοκ.
Δεν υπάρχει όριο στον αριθμό των ορισμάτων που μπορούν να περαστούν στο μπλοκ.
Για να χρησιμοποιήσεις τα ορίσματα μέσα στο μπλοκ, χρησιμοποιείς τη σύνταξη |x, y, ...|.
Όπου τα x, y, ... είναι τα ονόματα των ορισμάτων.
def my_method(&block)
1 + yield 2
end
# With curly braces:
my_method { |x| x * 3 }
# => 7
# With do ... end:
my_method do |x|
x * 3
end
# => 7
Στο παραπάνω παράδειγμα, στο μπλοκ δίνεται το όρισμα 2 και το μπλοκ το πολλαπλασιάζει επί 3 και επιστρέφει 6.
Στη συνέχεια, η μέθοδος προσθέτει 1 στο αποτέλεσμα και επιστρέφει 7.
Αν ο αριθμός των ορισμάτων που δίνονται στο yield είναι μεγαλύτερος από τον αριθμό των ορισμάτων που περιμένει το μπλοκ, θα πεταχτεί ένα σφάλμα.
def my_method(&block)
1 + yield
end
my_method { |x| x * 3 }
# Error: too many block parameters (given 1, expected maximum 0)
Τα μπλοκ μπορούν να γραφτούν ρητά ή έμμεσα. Τα έμμεσα μπλοκ είναι τα πιο συνηθισμένα και είναι αυτά που φαίνονται στα παραπάνω παραδείγματα. Μπορείς μάλιστα να δώσεις στο μπλοκ ορίσματα διαφορετικών τύπων και ο μεταγλωττιστής θα συμπεράνει τους τύπους και θα δημιουργήσει έναν τύπο ένωσης.
def my_method(&block)
yield "a"
yield 1
end
my_method do |x|
p typeof(x)
end
# output: (String | Int32)
Όταν γράφεις ρητά μπλοκ, μπορείς να προσδιορίσεις τους τύπους των ορισμάτων και τον τύπο επιστροφής.
Αυτό γίνεται με τη σύνταξη Arg1Type, Arg2Type, ... -> ResultType.
Όπου τα ορίσματα χωρίζονται με κόμματα(,), και ο τύπος αποτελέσματος χωρίζεται με ->.
def my_method(&block : Int32, Int32 -> Int32)
1 + yield(2, 3)
end
my_method { |x, y| x * y }
# => 7
Όπως και με άλλους ρητούς τύπους, ο μεταγλωττιστής θα επιβάλει οι τύποι να είναι οι αναμενόμενοι. Αν το μπλοκ δεν ταιριάζει με τον τύπο που δίνεται στη μέθοδο, θα πεταχτεί ένα σφάλμα.
Υπάρχει μια συντομογραφία για μπλοκ που παίρνουν μόνο ένα όρισμα και καλούν μόνο μία μέθοδο πάνω του.
Αντί να χρησιμοποιήσεις το |x|, μπορείς να βάλεις & πριν από το όνομα του ορίσματος.
def my_method(&block)
yield "a"
end
# This is:
my_method { |x| x.upcase }
# => "A"
# Is the same as:
my_method &.upcase
# => "A"
Το Proc έχει πολύ στενή σχέση με τα μπλοκ.
Η βασική διαφορά είναι ότι το Proc είναι τύπος, ενώ το μπλοκ όχι.
Το Proc αντιπροσωπεύει έναν δείκτη συνάρτησης και μπορεί να μεταφερθεί ως μεταβλητή.
Για να ορίσεις ένα Proc, χρησιμοποιείς τη σύνταξη του proc literal ->(args) { ... }.
Τα ορίσματα χωρίζονται με κόμματα(,), και το μπλοκ χωρίζεται με { ... }.
Για να καλέσεις ένα Proc, χρησιμοποιείς τη μέθοδο call και της περνάς τα ορίσματα.
Οι τύποι των παραμέτρων είναι υποχρεωτικοί.
Ο τύπος επιστροφής συμπεραίνεται από το σώμα του proc, αλλά μπορεί επίσης να δοθεί ρητά.
my_proc = ->(x : Int32, y : Int32) : Int32 { x * y }
my_proc.call(2, 3)
# => 6
Η σύλληψη μπλοκ είναι ένας τρόπος να δημιουργήσεις ένα Proc από ένα μπλοκ.
Αυτό γίνεται χρησιμοποιώντας τον τελεστή & πριν από το τελευταίο όρισμα μιας μεθόδου.
Αυτό θα μετατρέψει το μπλοκ σε Proc και θα το εκχωρήσει στο όρισμα.
def my_method(&block)
block.call
end
my_method { 1 + 2 }
# => 3
Τα Proc μπορούν να περαστούν σε μεθόδους που περιμένουν ένα μπλοκ. Αυτό είναι βολικό όταν θέλεις να μπορείς να περάσεις διαφορετικά μπλοκ ανάλογα με κάποια συνθήκη.
def my_method(&block)
yield 5
end
my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10
Όταν δηλώνεις μια μεταβλητή, μπορείς να χρησιμοποιήσεις τη σύνταξη : Type για να δηλώσεις τον τύπο της μεταβλητής.
Για τα Proc, χρησιμοποιείς τη σύνταξη Proc((ArgType, ...), ResultType).
Ο τελευταίος τύπος θα είναι πάντα ο τύπος επιστροφής και οι υπόλοιποι θα είναι οι τύποι των ορισμάτων.
Δηλαδή, αν υπάρχει μόνο ένας τύπος, θα είναι ο τύπος επιστροφής και το proc δεν θα παίρνει ορίσματα.
my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }
Μπορείς επίσης να δηλώσεις ένα proc από μια μέθοδο.
def my_method(a, b)
a + b
end
my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)