Треки
/
Crystal
Crystal
/
Салабус
/
Proc і блоки
Pr

Proc і блоки у Crystal

1 вправа

Про концепцію Proc і блоки

Блок - це фрагмент коду, який можна передати методу. В інших мовах програмування це називають анонімною функцією або лямбдою. Метод може перетворити блок на 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 - це ключове слово, яке виконує блок, переданий методу. Це означає, що блок ніколи не буде виконано, якщо не викликати 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, використовують синтаксис літерала 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 у методи

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 із методів

Також можна оголосити proc з методу.

def my_method(a, b)
  a + b
end

my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці

Вивчити концепцію Proc і блоки

Практика заблокована

Розблокуйте ще 1 вправу, щоб практикувати концепцію Proc і блоки