Блок - це фрагмент коду, який можна передати методу. В інших мовах програмування це називають анонімною функцією або лямбдою. Метод може перетворити блок на Proc, який представляє блок коду.
def my_method(&block)
1 + yield
end
my_method { 2 }
# => 3
Блоки та Proc дуже часто використовують у Crystal, наприклад для ітерації та зворотних викликів. Але також і для створення DSL (предметно-орієнтованих мов); прикладами DSL є вебфреймворк Kemal або фреймворк для тестування Spec (саме його використовують для тестування рішень на Crystal).
Щоб створити метод, який може приймати блок, потрібно додати амперсанд (&) перед останнім аргументом.
Це перетворить блок на 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 ->(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)