Trilhas
/
Crystal
Crystal
/
Programa
/
Procs e blocks
Pr

Procs e blocks em Crystal

1 exercício

Sobre Procs e blocks

Um bloco é um trecho de código que pode ser passado para um método. Em outras linguagens de programação, isso é conhecido como função anônima ou lambda. Um método pode converter um bloco em um Proc, que representa um bloco de código.

def my_method(&block)
  1 + yield
end

my_method { 2 }
# => 3

Blocos e Procs são usados o tempo todo em Crystal, para coisas como iteração e callbacks. Mas também para criar DSLs (linguagens específicas de domínio); alguns exemplos de DSLs são o framework web Kemal ou o framework de testes Spec (que é o que é usado para testar suas soluções em Crystal).

Blocos

Para criar um método que aceita um bloco, você precisa adicionar um e comercial (&) antes do último argumento. Isso vai converter o bloco em um Proc e atribuí-lo ao argumento. Depois, usar yield executa o bloco passado para o método.

def my_method(&block)
  1 + yield
end

Há duas formas de passar um bloco: usando as palavras-chave do ... end ou usando chaves ({}). A principal diferença entre as duas é a precedência: o do ... end aceita blocos de várias linhas, enquanto as chaves ({}) não.

my_method do
  2
end
# => 3

my_method { 2 }
# => 3

Yield

yield é uma palavra-chave que executa o bloco passado para o método. Isso significa que o bloco nunca será executado se yield não for chamado. Também é possível passar argumentos para ele, que serão repassados ao bloco. Não há limite para o número de argumentos que podem ser passados ao bloco. Para usar os argumentos no bloco, você usa a sintaxe |x, y, ...|, onde x, y, ... são os nomes dos argumentos.

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

No exemplo acima, o bloco recebe o argumento 2, multiplica-o por 3 e retorna 6. Depois, o método soma 1 ao resultado e retorna 7.

Se o número de argumentos passados para yield for maior que o número de argumentos que o bloco espera, um erro será lançado.

def my_method(&block)
  1 + yield
end

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

Tipos de blocos

Blocos podem ser escritos de forma explícita ou implícita. Os blocos implícitos são os mais comuns e são os que aparecem nos exemplos acima. Você pode até passar tipos de argumentos diferentes para o bloco, e o compilador vai inferir os tipos e criar um tipo união.

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

my_method do |x|
  p typeof(x)
end

# output: (String | Int32)

Ao escrever blocos explícitos, você pode especificar os tipos dos argumentos e o tipo de retorno. Isso é feito com a sintaxe Arg1Type, Arg2Type, ... -> ResultType. Nela, os argumentos são separados por vírgulas (,) e o tipo do resultado é separado por ->.

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

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

Assim como acontece com outros tipos explícitos, o compilador vai garantir que os tipos sejam os esperados. Se o bloco não corresponder ao tipo informado ao método, um erro será lançado.

Forma abreviada

Existe uma forma abreviada para blocos que recebem apenas um argumento e chamam apenas um método nele. Em vez de usar |x|, você pode usar & antes do nome do argumento.

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 tem uma conexão muito forte com blocos. A principal diferença é que Proc é um tipo e um bloco não é. Um Proc representa um ponteiro de função e pode ser passado por aí como uma variável.

Para definir um Proc, você usa a sintaxe do literal de proc ->(args) { ... }. Os argumentos são separados por vírgulas (,) e o bloco é separado por { ... }. Para invocar um Proc, você usa o método call e passa os argumentos para ele. Os tipos dos parâmetros são obrigatórios. O tipo de retorno é inferido a partir do corpo do proc, mas também pode ser informado explicitamente.

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

Capturando blocos

Capturar blocos é uma forma de criar um Proc a partir de um bloco. Isso é feito usando o operador & antes do último argumento de um método. Isso vai converter o bloco em um Proc e atribuí-lo ao argumento.

def my_method(&block)
  block.call
end

my_method { 1 + 2 }
# => 3

Passando procs para métodos

Procs podem ser passados para métodos que esperam um bloco. Isso pode ser útil quando você quer poder passar blocos diferentes com base em alguma condição.

def my_method(&block)
  yield 5
end

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

Declarando o tipo de variáveis

Ao declarar uma variável, você pode usar a sintaxe : Type para declarar o tipo dela. Para procs, você usa a sintaxe Proc((ArgType, ...), ResultType). O último tipo será sempre o tipo de retorno e os demais serão os tipos dos argumentos. Ou seja, se houver apenas um tipo, ele será o tipo de retorno e o proc não receberá nenhum argumento.

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

Declarar procs a partir de métodos

Você também pode declarar um proc a partir de um método.

def my_method(a, b)
  a + b
end

my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)
Editar via GitHub O link abre em uma nova janela ou aba

Aprenda Procs e blocks

A prática está bloqueada

Desbloqueie mais 1 exercício para praticar Procs e blocks