Ένα μπλοκ είναι ένα κομμάτι κώδικα που μπορεί να περαστεί σε μια μέθοδο. Αυτό είναι γνωστό ως ανώνυμη συνάρτηση ή λάμδα σε άλλες γλώσσες προγραμματισμού. Μια μέθοδος μπορεί να μετατρέψει ένα μπλοκ σε Proc, το οποίο αναπαριστά ένα μπλοκ κώδικα.
def my_method(&block)
1 + yield
end
my_method { 2 }
# => 3
Τα μπλοκ και τα Proc χρησιμοποιούνται πολύ συχνά στη Crystal, για πράγματα όπως η επανάληψη και τα callbacks. Αλλά και για να δημιουργήσεις DSL (Γλώσσες Ειδικού Σκοπού). Κάποια παραδείγματα DSL είναι το web framework Kemal ή το testing 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
Όπως συμβαίνει και με άλλους ρητούς τύπους, ο μεταγλωττιστής θα επιβάλει οι τύποι να είναι αυτοί που αναμένονται. Αν το μπλοκ δεν ταιριάζει με τον τύπο που δίνεται στη μέθοδο, θα πεταχτεί ένα σφάλμα.
Το 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 }
Αναπτύσσεις ένα σύστημα διαχείρισης περιεχομένου για έναν ιστότοπο που φιλοξενεί άρθρα, αναρτήσεις ιστολογίου και άλλο γραπτό περιεχόμενο. Σου έχει ανατεθεί να υλοποιήσεις έναν χειριστή εργασιών που θα επεξεργάζεται εργασίες σε μια ουρά προτεραιότητας. Ο χειριστής εργασιών θα λαμβάνει εργασίες και, με βάση τον φόρτο του διακομιστή και την προτεραιότητά τους, θα αποφασίζει ποια εργασία θα εκτελέσει στη συνέχεια. Αν ο διακομιστής είναι υπό μεγάλο φόρτο, τότε ο αριθμός προτεραιότητας πρέπει να είναι υψηλότερος για να εκτελεστεί η εργασία. Ο διακομιστής θα αλλάζει δυναμικά τον φόρτο με βάση τον φόρτο.
TaskHandler
Ο TaskHandler πρέπει να αρχικοποιηθεί με μια ουρά και ένα προεπιλεγμένο load_level.
Υλοποίησε τον αρχικοποιητή της κλάσης TaskHandler που παίρνει ως όρισμα έναν Array από String με τα ονόματα των εργασιών και τον αποθηκεύει σε μια μεταβλητή στιγμιοτύπου με το όνομα @tasks.
Ο αρχικοποιητής θα πρέπει επίσης να δημιουργεί τη μεταβλητή στιγμιοτύπου @task_condition_logic που αποθηκεύει ένα Proc το οποίο παίρνει ένα Int32 και επιστρέφει αληθής αν το όρισμα είναι ίσο ή μεγαλύτερο από το 0 και ψευδής αν είναι μικρότερο από το 0.
task_handler = TaskHandler.new(["task1", "task2", "task3"])
# => #<TaskHandler:0x7fd8d4cfbf00 @tasks=["task1", "task2", "task3"], @task_condition_logic=#<Proc(Int32, Bool):0x562536a4a330>>
Ο διακομιστής πρέπει να μπορεί να ενημερώνει τη λογική συνθήκης εργασιών με βάση τον φόρτο του διακομιστή.
Υλοποίησε τη μέθοδο TaskHandler#update_task_condition_logic που παίρνει ένα μπλοκ ως όρισμα και το μπλοκ θα πρέπει να αποθηκεύεται στη μεταβλητή στιγμιοτύπου @task_condition_logic.
task_handler = TaskHandler.new(["task1", "task2", "task3"])
task_handler.update_task_condition_logic { |load| load > 1 }
# => #<TaskHandler:0x7f068118cf00 @tasks=["task1", "task2", "task3"], @task_condition_logic=#<Proc(Int32, Bool):0x562df99d0370>>
Ο διακομιστής πρέπει να μπορεί να εκτελέσει την επόμενη εργασία στην ουρά.
Η πρώτη εργασία στον πίνακα @tasks είναι η επόμενη εργασία που θα εκτελεστεί.
Η εργασία θα πρέπει να εκτελείται αν το επίπεδο προτεραιότητας της δεδομένης εργασίας, όταν περαστεί στο proc @task_condition_logic, επιστρέφει αληθής.
Θα επιστρέφει μηνύματα ανάλογα με το αν η εργασία εκτελέστηκε ή όχι.
Αν η εργασία εκτελέστηκε, θα πρέπει να επιστρέφει: "Completed <task>".
Αν η εργασία δεν εκτελέστηκε, θα πρέπει να επιστρέφει: "Could not complete <task>".
Αν η εργασία εκτελέστηκε, θα πρέπει να αφαιρείται από τον πίνακα @tasks.
Υλοποίησε τη μέθοδο TaskHandler#execute_next_task που παίρνει ως όρισμα το επίπεδο προτεραιότητας της επόμενης εργασίας ως Int32 και επιστρέφει ένα String ανάλογα με το αν η εργασία εκτελέστηκε ή όχι.
task_handler = TaskHandler.new(["task1", "task2", "task3"])
task_handler.update_task_condition_logic { |load| load > 1 }
task_handler.execute_next_task(2)
# => "Completed task1"
task_handler.execute_next_task(1)
# => "Could not complete task2"
Γράψου στο Exercism για να μάθεις και να κατακτήσεις Crystal με 26 έννοιες142 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.