Trilhas
/
Crystal
Crystal
/
Exercícios
/
Manipulador de tarefas
Manipulador de tarefas

Manipulador de tarefas

Exercício de aprendizagem

Introdução

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).

Blocos

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

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)

Tipos de blocos

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.

Procs

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

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 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 }

Instruções

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.

1. Inicialize 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>>

2. Atualize a lógica de condição da tarefa

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>>

3. Execute a próxima tarefa

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"
Editar via GitHub O link abre em uma nova janela ou aba
Crystal Exercism

Tudo pronto para começar Manipulador de tarefas?

Crie sua conta no Exercism para aprender e dominar Crystal com 26 conceitos142 exercícios e mentoria humana de verdade, tudo de graça.