Un blocco è un pezzo di codice che può essere passato a un metodo. In altri linguaggi di programmazione è noto come funzione anonima o lambda. Un metodo può convertire un blocco in un Proc, che rappresenta un blocco di codice.
def my_method(&block)
1 + yield
end
my_method { 2 }
# => 3
Blocchi e Proc sono usatissimi in Crystal, per cose come l'iterazione e le callback. Ma anche per creare DSL (Domain Specific Languages): alcuni esempi di DSL sono il framework web Kemal o il framework di testing Spec (quello che viene usato per testare le soluzioni in Crystal).
Per creare un metodo che possa accettare un blocco devi aggiungere una e commerciale(&) prima dell'ultimo argomento.
Questo convertirà il blocco in un Proc e lo assegnerà all'argomento.
Poi, usando yield, eseguirai il blocco dato al metodo.
def my_method(&block)
1 + yield
end
Per passare un blocco ci sono due modi: usare le parole chiave do ... end oppure le parentesi graffe({}).
La differenza principale tra i due è la precedenza: do ... end supporta blocchi su più righe, mentre le parentesi graffe({}) no.
my_method do
2
end
# => 3
my_method { 2 }
# => 3
yield è una parola chiave che esegue il blocco dato al metodo.
Questo significa che il blocco non verrà mai eseguito se yield non viene chiamato.
Può anche ricevere argomenti, che verranno passati al blocco.
Non c'è limite al numero di argomenti che si possono passare al blocco.
Per usare gli argomenti nel blocco usi la sintassi |x, y, ...|.
Qui x, y, ... sono i nomi degli argomenti.
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
Nell'esempio qui sopra al blocco viene dato l'argomento 2, il blocco lo moltiplica per 3 e restituisce 6.
Poi il metodo aggiunge 1 al risultato e restituisce 7.
Se il numero di argomenti dati a yield è maggiore del numero di argomenti che il blocco si aspetta, viene generato un errore.
def my_method(&block)
1 + yield
end
my_method { |x| x * 3 }
# Error: too many block parameters (given 1, expected maximum 0)
I blocchi possono essere scritti in modo esplicito o implicito. I blocchi impliciti sono i più comuni e sono quelli mostrati negli esempi qui sopra. Puoi anche dare al blocco argomenti di tipi diversi: il compilatore dedurrà i tipi e creerà un tipo unione.
def my_method(&block)
yield "a"
yield 1
end
my_method do |x|
p typeof(x)
end
# output: (String | Int32)
Quando scrivi blocchi espliciti puoi specificare i tipi degli argomenti e il tipo restituito.
Si fa con la sintassi Arg1Type, Arg2Type, ... -> ResultType.
Qui gli argomenti sono separati da virgole(,), e il tipo del risultato è separato da ->.
def my_method(&block : Int32, Int32 -> Int32)
1 + yield(2, 3)
end
my_method { |x, y| x * y }
# => 7
Come con gli altri tipi espliciti, il compilatore farà rispettare i tipi attesi. Se il blocco non corrisponde al tipo dato al metodo, viene generato un errore.
Proc ha un legame molto forte con i blocchi.
La differenza chiave è che un Proc è un tipo, mentre un blocco no.
Un Proc rappresenta un puntatore a funzione e può essere passato in giro come una variabile.
Per definire un Proc usi la sintassi del letterale proc ->(args) { ... }.
Gli argomenti sono separati da virgole(,), e il blocco è delimitato da { ... }.
Per chiamare un Proc usi il metodo call e gli passi gli argomenti.
I tipi dei parametri sono obbligatori.
Il tipo restituito viene dedotto dal corpo del proc, ma può anche essere indicato esplicitamente.
my_proc = ->(x : Int32, y : Int32) : Int32 { x * y }
my_proc.call(2, 3)
# => 6
Catturare i blocchi è un modo per creare un Proc da un blocco.
Si fa usando l'operatore & prima dell'ultimo argomento di un metodo.
Questo convertirà il blocco in un Proc e lo assegnerà all'argomento.
def my_method(&block)
block.call
end
my_method { 1 + 2 }
# => 3
I Proc possono essere passati a metodi che si aspettano un blocco. Può tornare utile quando vuoi poter passare blocchi diversi in base a qualche condizione.
def my_method(&block)
yield 5
end
my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10
Quando dichiari una variabile puoi usare la sintassi : Type per dichiarare il tipo della variabile.
Per i proc usi la sintassi Proc((ArgType, ...), ResultType).
L'ultimo tipo è sempre il tipo restituito, mentre gli altri sono i tipi degli argomenti.
Questo significa che se c'è un solo tipo, sarà il tipo restituito e il proc non accetterà argomenti.
my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }
Stai sviluppando un sistema di gestione dei contenuti per un sito web che ospita articoli, post di blog e altri contenuti scritti. Il tuo compito è implementare un task handler che elabori le attività in una coda con priorità. Il task handler riceverà le attività e, in base al carico del server e alla loro priorità, deciderà quale attività eseguire per prima. Se il server è sotto un carico elevato, il numero di priorità deve essere più alto perché l'attività venga eseguita. Il server cambierà dinamicamente il carico in base al carico.
TaskHandler
TaskHandler deve essere inizializzato con una coda e un load_level predefinito.
Implementa l'inizializzatore della classe TaskHandler, che accetta come argomento un Array di String con i nomi delle attività e lo memorizza in una variabile d'istanza chiamata @tasks.
L'inizializzatore deve anche creare la variabile d'istanza @task_condition_logic, che memorizza un Proc che accetta un Int32 e restituisce true se l'argomento è uguale o maggiore di 0 e false se è minore di 0.
task_handler = TaskHandler.new(["task1", "task2", "task3"])
# => #<TaskHandler:0x7fd8d4cfbf00 @tasks=["task1", "task2", "task3"], @task_condition_logic=#<Proc(Int32, Bool):0x562536a4a330>>
Il server deve poter aggiornare la logica della condizione dell'attività in base al carico del server.
Implementa il metodo TaskHandler#update_task_condition_logic, che accetta un blocco come argomento; il blocco deve essere memorizzato nella variabile d'istanza @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>>
Il server deve poter eseguire l'attività successiva nella coda.
La prima attività nell'array @tasks è la prossima da eseguire.
L'attività deve essere eseguita se il livello di priorità passato al proc @task_condition_logic restituisce true.
Restituirà messaggi diversi a seconda che l'attività sia stata eseguita o meno.
Se l'attività è stata eseguita, deve restituire: "Completed <task>".
Se l'attività non è stata eseguita, deve restituire: "Could not complete <task>".
Se l'attività è stata eseguita, deve essere rimossa dall'array @tasks.
Implementa il metodo TaskHandler#execute_next_task, che accetta come argomento il livello di priorità della prossima attività come Int32 e restituisce una String a seconda che l'attività sia stata eseguita o meno.
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"
Iscriviti a Exercism per imparare e padroneggiare Crystal con 26 concetti142 esercizi e il mentoring di persone reali, tutto gratis.