Parcours
/
Crystal
Crystal
/
Programme
/
Procs et blocs
Pr

Procs et blocs en Crystal

1 exercice

À propos de Procs et blocs

Un bloc est un morceau de code que l'on peut passer à une méthode. Dans d'autres langages de programmation, on appelle cela une fonction anonyme ou lambda. Une méthode peut convertir un bloc en un Proc, qui représente un bloc de code.

def my_method(&block)
  1 + yield
end

my_method { 2 }
# => 3

Les blocs et les Proc sont très couramment utilisés en Crystal, notamment pour l'itération et les callbacks. Mais aussi pour créer des DSL (langages dédiés) ; quelques exemples de DSL sont le framework web Kemal ou le framework de test Spec (qui est celui utilisé pour tester tes solutions Crystal).

Blocs

Pour créer une méthode qui peut accepter un bloc, il faut ajouter une esperluette (&) avant le dernier argument. Cela convertit le bloc en un Proc et l'affecte à cet argument. Ensuite, utiliser yield exécute le bloc donné à la méthode.

def my_method(&block)
  1 + yield
end

Pour passer un bloc, il y a deux façons : soit avec les mots-clés do ... end, soit avec des accolades ({}). La principale différence entre les deux est la priorité : do ... end prend en charge les blocs sur plusieurs lignes, alors que les accolades ({}) ne le font pas.

my_method do
  2
end
# => 3

my_method { 2 }
# => 3

Yield

yield est un mot-clé qui exécute le bloc donné à la méthode. Cela signifie que le bloc ne sera jamais exécuté si yield n'est pas appelé. On peut aussi lui donner des arguments, qui seront transmis au bloc. Il n'y a pas de limite au nombre d'arguments que l'on peut passer au bloc. Pour utiliser les arguments dans le bloc, on utilise la syntaxe |x, y, ...|, où x, y, ... sont les noms des arguments.

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

Dans l'exemple ci-dessus, l'argument 2 est donné au bloc ; le bloc le multiplie par 3 et renvoie 6. La méthode ajoute ensuite 1 au résultat et renvoie 7.

Si le nombre d'arguments donnés à yield est supérieur au nombre d'arguments attendus par le bloc, une erreur est levée.

def my_method(&block)
  1 + yield
end

my_method { |x| x * 3 }
# Error: too many block parameters (given 1, expected maximum 0)

Types de blocs

Les blocs peuvent être écrits de manière explicite ou implicite. Les blocs implicites sont les plus courants, et ce sont ceux des exemples ci-dessus. On peut même donner au bloc des arguments de types différents : le compilateur inférera les types et créera un type union.

def my_method(&block)
  yield "a"
  yield 1
end

my_method do |x|
  p typeof(x)
end

# output: (String | Int32)

Quand on écrit des blocs explicites, on peut préciser les types des arguments et le type de retour. Cela se fait avec la syntaxe Arg1Type, Arg2Type, ... -> ResultType, où les arguments sont séparés par des virgules (,), et le type du résultat par ->.

def my_method(&block : Int32, Int32 -> Int32)
  1 + yield(2, 3)
end

my_method { |x, y| x * y }
# => 7

Comme pour les autres types explicites, le compilateur vérifie que les types correspondent à ce qui est attendu. Si le bloc ne correspond pas au type donné à la méthode, une erreur est levée.

Raccourci

Il existe un raccourci pour les blocs qui ne prennent qu'un seul argument et n'appellent qu'une seule méthode dessus. Au lieu d'utiliser |x|, on peut utiliser & devant le nom de l'argument.

def my_method(&block)
  yield "a"
end

# This is: 

my_method { |x| x.upcase }
# => "A"

# Is the same as:

my_method &.upcase
# => "A"

Procs

Proc a un lien très fort avec les blocs. La différence essentielle est qu'un Proc est un type, alors qu'un bloc n'en est pas un. Un Proc représente un pointeur de fonction et peut être transmis comme une variable.

Pour définir un Proc, on utilise la syntaxe du littéral de proc ->(args) { ... }. Les arguments sont séparés par des virgules (,), et le bloc est délimité par { ... }. Pour invoquer un Proc, on utilise la méthode call et on lui passe les arguments. Les types des paramètres sont obligatoires. Le type de retour est inféré à partir du corps du proc, mais peut aussi être fourni explicitement.

my_proc = ->(x : Int32, y : Int32) : Int32 { x * y }
my_proc.call(2, 3)
# => 6

Capture des blocs

Capture des blocs est une façon de créer un Proc à partir d'un bloc. Cela se fait en utilisant l'opérateur & devant le dernier argument d'une méthode. Cela convertit le bloc en un Proc et l'affecte à cet argument.

def my_method(&block)
  block.call
end

my_method { 1 + 2 }
# => 3

Passe des procs à des méthodes

On peut passer des procs à des méthodes qui attendent un bloc. C'est pratique quand on veut pouvoir passer différents blocs selon une condition.

def my_method(&block)
  yield 5
end

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

Déclare le type des variables

Quand on déclare une variable, on peut utiliser la syntaxe : Type pour déclarer le type de la variable. Pour les procs, on utilise la syntaxe Proc((ArgType, ...), ResultType). Le dernier type est toujours le type de retour, et les autres sont les types des arguments. Autrement dit, s'il n'y a qu'un seul type, c'est le type de retour et le proc ne prend aucun argument.

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

Déclare des procs à partir de méthodes

On peut aussi déclarer un proc à partir d'une méthode.

def my_method(a, b)
  a + b
end

my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet

Apprends Procs et blocs

L'entraînement est verrouillé

Déverrouille 1 exercice de plus pour t'entraîner sur Procs et blocs