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 muito usados em Crystal, para coisas como iteração e callbacks. Mas também para criar DSLs (linguagens de domínio específico); alguns exemplos de DSLs são o framework web Kemal ou o framework de testes Spec (que é o usado para testar suas soluções em Crystal).
Para criar um método que aceite 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 vai executar o bloco passado ao método.
def my_method(&block)
1 + yield
end
Para passar um bloco, há duas formas: usar as palavras-chave do ... end ou usar chaves ({}).
A principal diferença entre as duas é a precedência: do ... end aceita blocos de várias linhas, enquanto as chaves ({}) não.
my_method do
2
end
# => 3
my_method { 2 }
# => 3
yield é uma palavra-chave que executa o bloco passado ao 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, ...|.
Aí, 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.
Em seguida, 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)
Blocos podem ser escritos de forma explícita ou implícita. Blocos implícitos são os mais comuns e são os que aparecem nos exemplos acima. Você pode até passar ao bloco argumentos de tipos diferentes, e o compilador vai inferir os tipos e criar uma união de tipos.
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.
Aí, 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 passado ao método, um erro será lançado.
Proc tem uma ligação muito forte com blocos.
A principal diferença é que um 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 de literal de proc ->(args) { ... }.
Os argumentos são separados por vírgulas (,) e o bloco é delimitado 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
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
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
Ao declarar uma variável, você pode usar a sintaxe : Type para declarar o tipo da variável.
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á argumentos.
my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }
Você está desenvolvendo um sistema de gerenciamento de conteúdo para um site que hospeda artigos, posts de blog e outros conteúdos escritos. Sua tarefa é implementar um manipulador de tarefas que processa tarefas em uma fila de prioridade. O manipulador de tarefas recebe tarefas e, com base na carga do servidor e na prioridade delas, decide qual tarefa executar em seguida. Se o servidor estiver sob carga pesada, o número de prioridade precisa ser maior para que a tarefa seja executada. O servidor vai mudar a carga dinamicamente com base na carga.
TaskHandler
O TaskHandler precisa ser inicializado com uma fila e um load_level padrão.
Implemente o inicializador da classe TaskHandler, que recebe um Array de Strings com os nomes das tarefas como argumento e o armazena em uma variável de instância chamada @tasks.
O inicializador também deve criar a variável de instância @task_condition_logic, que armazena um Proc que recebe um Int32 e retorna true se o argumento for maior ou igual a 0 e false se for menor que 0.
task_handler = TaskHandler.new(["task1", "task2", "task3"])
# => #<TaskHandler:0x7fd8d4cfbf00 @tasks=["task1", "task2", "task3"], @task_condition_logic=#<Proc(Int32, Bool):0x562536a4a330>>
O servidor precisa conseguir atualizar a lógica de condição da tarefa com base na carga do servidor.
Implemente o método TaskHandler#update_task_condition_logic, que recebe um bloco como argumento, e esse bloco deve ser armazenado na variável de instância @task_condition_logic.
task_handler = TaskHandler.new(["task1", "task2", "task3"])
task_handler.update_task_condition_logic { |load| load > 1 }
# => #<TaskHandler:0x7f068118cf00 @tasks=["task1", "task2", "task3"], @task_condition_logic=#<Proc(Int32, Bool):0x562df99d0370>>
O servidor precisa conseguir executar a próxima tarefa da fila.
A primeira tarefa do array @tasks é a próxima tarefa a ser executada.
A tarefa deve ser executada se o nível de prioridade informado para a tarefa retornar true quando passado ao proc @task_condition_logic.
Ele deve retornar mensagens conforme a tarefa tenha sido executada ou não.
Se a tarefa foi executada, ele deve retornar: "Completed <task>".
Se a tarefa não foi executada, ele deve retornar: "Could not complete <task>".
Se a tarefa foi executada, ela deve ser removida do array @tasks.
Implemente o método TaskHandler#execute_next_task, que recebe o nível de prioridade como Int32 da próxima tarefa como argumento e retorna uma String conforme a tarefa tenha sido executada ou não.
task_handler = TaskHandler.new(["task1", "task2", "task3"])
task_handler.update_task_condition_logic { |load| load > 1 }
task_handler.execute_next_task(2)
# => "Completed task1"
task_handler.execute_next_task(1)
# => "Could not complete task2"
Crie sua conta no Exercism para aprender e dominar Crystal com 26 conceitos142 exercícios e mentoria humana de verdade, tudo de graça.