Tracce
/
Crystal
Crystal
/
Programma
/
Proc e blocchi
Pr

Proc e blocchi in Crystal

1 esercizio

Informazioni su Proc e blocchi

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 una Proc, che rappresenta un blocco di codice.

def my_method(&block)
  1 + yield
end

my_method { 2 }
# => 3

I blocchi e le Proc sono usati molto comunemente in Crystal, per cose come l'iterazione e le callback. Ma anche per creare DSL (linguaggi specifici di dominio): alcuni esempi di DSL sono il framework web Kemal o il framework di test Spec (che è quello usato per testare le soluzioni in Crystal).

Blocchi

Per creare un metodo che accetti un blocco devi aggiungere una e commerciale (&) prima dell'ultimo argomento. Questo convertirà il blocco in una Proc e lo assegnerà all'argomento. Poi, usando yield si eseguirà il blocco passato 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 i blocchi su più righe, mentre le parentesi graffe ({}) no.

my_method do
  2
end
# => 3

my_method { 2 }
# => 3

Yield

yield è una parola chiave che esegue il blocco passato 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 si usa la sintassi |x, y, ...|. Dove 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 passati a yield è maggiore del numero di argomenti che il blocco si aspetta, verrà 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)

Tipi di blocchi

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 diversi tipi di argomenti e 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. Questo 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 verificherà che i tipi siano quelli attesi. Se il blocco non corrisponde al tipo dato al metodo, verrà generato un errore.

Forma abbreviata

Esiste una forma abbreviata per i blocchi che prendono un solo argomento e vi chiamano un solo metodo. Invece di usare |x| puoi usare & prima del nome dell'argomento.

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 ha un legame molto forte con i blocchi. La differenza principale è che una Proc è un tipo, mentre un blocco no. Una Proc rappresenta un puntatore a funzione e può essere passata in giro come variabile.

Per definire una Proc si usa il letterale proc con la sintassi ->(args) { ... }. Gli argomenti sono separati da virgole (,), e il blocco è separato da { ... }. Per chiamare una Proc si usa il metodo call e vi si passano gli argomenti. I tipi dei parametri sono obbligatori. Il tipo restituito viene dedotto dal corpo della 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

Catturare i blocchi è un modo per creare una Proc a partire da un blocco. Questo si fa usando l'operatore & prima dell'ultimo argomento di un metodo. In questo modo il blocco viene convertito in una Proc e assegnato all'argomento.

def my_method(&block)
  block.call
end

my_method { 1 + 2 }
# => 3

Passare le proc ai metodi

Le Proc possono essere passate ai metodi che si aspettano un blocco. Questo può essere utile quando si vogliono passare blocchi diversi in base a una condizione.

def my_method(&block)
  yield 5
end

my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10

Dichiarare il tipo delle variabili

Quando dichiari una variabile puoi usare la sintassi : Type per dichiararne il tipo. Per le proc, si usa la sintassi Proc((ArgType, ...), ResultType). L'ultimo tipo sarà sempre il tipo restituito, mentre gli altri saranno i tipi degli argomenti. Se c'è un solo tipo, questo sarà il tipo restituito e la proc non accetterà argomenti.

my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }

Dichiarare proc a partire dai metodi

Puoi anche dichiarare una proc a partire da un metodo.

def my_method(a, b)
  a + b
end

my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)
Modifica tramite GitHub Il collegamento si apre in una nuova finestra o scheda

Impara Proc e blocchi

La pratica è bloccata

Sblocca un altro esercizio per esercitarti su Proc e blocchi