块是一段可以传给方法的代码。 在其他编程语言中,这被称为匿名函数或 lambda。 方法可以把块转换成 Proc,Proc 表示一段代码。
def my_method(&block)
1 + yield
end
my_method { 2 }
# => 3
块和 Proc 在 Crystal 中非常常用,比如用于迭代和回调。 它们也常用来创建 DSL(领域特定语言),DSL 的例子包括 Web 框架 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
和其他显式类型一样,编译器也会强制类型符合预期。 如果块与方法所给的类型不匹配,就会抛出错误。
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 }
你正在为一个托管文章、博客文章和其他文字内容的网站开发内容管理系统。 你的任务是实现一个任务处理器,用来处理优先队列中的任务。 这个任务处理器会接收任务,并根据服务器的负载和这些任务的优先级,决定接下来执行哪个任务。 如果服务器负载很高,那么任务的优先级数字必须更高,才会被执行。 服务器会根据负载动态地改变负载。
TaskHandler
需要用队列和一个默认的 load_level 来初始化 TaskHandler。
实现TaskHandler类的初始化方法,它接受一个由各个任务名称组成的String的Array作为形参,并把它存入名为@tasks的实例变量。
这个初始化方法还应该创建实例变量@task_condition_logic,它存储一个Proc,该Proc接受一个Int32,如果实参大于或等于 0 就返回 true,小于 0 就返回 false。
task_handler = TaskHandler.new(["task1", "task2", "task3"])
# => #<TaskHandler:0x7fd8d4cfbf00 @tasks=["task1", "task2", "task3"], @task_condition_logic=#<Proc(Int32, Bool):0x562536a4a330>>
服务器必须能够根据服务器的负载来更新任务条件逻辑。
实现TaskHandler#update_task_condition_logic方法,它接受一个代码块作为形参,这个代码块应该被存入实例变量@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>>
服务器必须能够执行队列中的下一个任务。
@tasks数组中的第一个任务就是下一个要执行的任务。
如果把给定任务的优先级传给@task_condition_logic这个 proc 后返回 true,就应该执行这个任务。
它会根据任务是否被执行来返回相应的消息。
如果任务被执行了,它应该返回:"Completed <task>"。
如果任务没有被执行,它应该返回:"Could not complete <task>"。
如果任务被执行了,就应该把它从@tasks数组中移除。
实现TaskHandler#execute_next_task方法,它接受下一个任务的优先级作为形参,优先级类型为Int32,并根据任务是否被执行返回一个String。
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"