进程和块

下面的方法会输出 Hello两次 。

  1. def twice # 使用yield关键词定义块方法,编译器可以识别到
  2. yield
  3. yield
  4. end
  5. twice do
  6. puts "Hello!" # 块代码
  7. end
  8. # 更加直观的写法, 使用 &
  9. def twice(&block)
  10. yield
  11. yield
  12. end

调用块方法使用 do…end 或 {}, 两者一般情况下是等价的。

  1. twice() do
  2. puts "Hello!"
  3. end
  4. twice do
  5. puts "Hello!"
  6. end
  7. twice { puts "Hello!" }

区别在于 do…end使用left most模式, {}使用right most模式。

  1. foo bar do
  2. something
  3. end
  4. # The above is the same as
  5. foo(bar) do
  6. something
  7. end
  8. foo bar { something } # same as : foo(bar { something })
  9. # 多个参数 , 实际传入的参数入量可以少于yield后面的参数数量
  10. def many
  11. yield 1, 2, 3
  12. end
  13. many do |x, y, z|
  14. puts x + y + z
  15. end
  16. # 单参数调用简写
  17. method do |argument|
  18. argument.some_method
  19. end
  20. # 同上
  21. method &.some_method
  22. method &.some_method(arg1, arg2)
  23. # 操作符也行。。。
  24. method &.+(2)
  25. method &.[index]
  26. # 类型限制
  27. def transform_int(start : Int32, &block : Int32 -> Int32)
  28. result = yield start
  29. result * 2
  30. end
  31. transform_int(3) { |x| x + 2 } #=> 10
  32. transform_int(3) { |x| "foo" } # Error: expected block to return Int32, not String
  33. # break ...略
  34. # next ...略
  35. # with...yield ...略
  36. # 块参数展开, 块代码参数可以被小括号展开
  37. array = [{1, "one"}, {2, "two"}]
  38. array.each do |(number, word)|
  39. puts "#{number}: #{word}"
  40. end
  41. # 上面的等价代码
  42. array = [{1, "one"}, {2, "two"}]
  43. array.each do |arg|
  44. number = arg[0]
  45. word = arg[1]
  46. puts "#{number}: #{word}"
  47. end

捕获(生成)块

块代码可以转换成一个带有上下文的进程。必须把块当成一个方法的block参数并指定其输入输出的参数类型。

  1. def int_to_int(&block : Int32 -> Int32)
  2. block
  3. end
  4. proc = int_to_int { |x| x + 1 }
  5. proc.call(1) #=> 2
  6. # 不指定返回类型,将没有返回
  7. def some_proc(&block : Int32 ->)
  8. block
  9. end
  10. proc = some_proc { |x| x + 1 }
  11. proc.call(1) # void
  12. # 使用下划线 返回任意类型值
  13. def some_proc(&block : Int32 -> _)
  14. block
  15. end
  16. proc = some_proc { |x| x + 1 }
  17. proc.call(1) # 2
  18. proc = some_proc { |x| x.to_s }
  19. proc.call(1) # "1"

注: return和break不能使用在block中。

Proc字面量

  1. def some_proc(&block : Int32 -> Int32)
  2. block
  3. end
  4. x = 0
  5. proc = ->(i : Int32) { x += i } # 字面量
  6. proc = some_proc(&proc)
  7. proc.call(1) #=> 1
  8. proc.call(10) #=> 11
  9. x #=> 11
  10. # 进程也可以从现有的方法创建
  11. def add(x, y)
  12. x + y
  13. end
  14. adder = ->add(Int32, Int32)
  15. adder.call(1, 2) #=> 3

块的转发执行

在yield可用的情况下,应避免使用闭包写法。并且break,next在捕获的闭包中无法使用。

  1. # 使用yield进行转发
  2. def foo
  3. yield 1
  4. end
  5. def wrap_foo
  6. puts "Before foo"
  7. foo do |x|
  8. yield x
  9. end
  10. puts "After foo"
  11. end
  12. wrap_foo do |i|
  13. puts i
  14. end
  15. # 使用闭包转发, 效率会降低
  16. def foo
  17. yield 1
  18. end
  19. def wrap_foo(&block : Int32 -> _)
  20. puts "Before foo"
  21. foo(&block)
  22. puts "After foo"
  23. end
  24. wrap_foo do |i|
  25. puts i
  26. end

闭包

捕获块和proc字面量隔离self和本地变量形成闭包,下面的例子来说明

  1. x = 0
  2. proc = ->{ x += 1; x }
  3. proc.call #=> 1
  4. proc.call #=> 2
  5. x #=> 2

也可以是在方法中返回一个进程

  1. def counter
  2. x = 0
  3. ->{ x += 1; x }
  4. end
  5. proc = counter
  6. proc.call #=> 1
  7. proc.call #=> 2
  8. # 上面的代码, x被proc字面量捕获并放置于堆中作为proc的上下文。一般情况下本地变量被放入栈中 并且在方法结束时被释放。

别名

使用别名可以给类型指定一个其它名字, 用于方便书写。

  1. alias PInt32 = Pointer(Int32)
  2. ptr = PInt32.malloc(1) # : Pointer(Int32)
  3. #
  4. alias RecArray = Array(Int32) | Array(RecArray)
  5. ary = [] of RecArray
  6. ary.push [1, 2, 3]
  7. ary.push ary
  8. ary #=> [[1, 2, 3], [...]]