进程和块

进程和块

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

def twice  # 使用yield关键词定义块方法,编译器可以识别到
  yield
  yield
end

twice do
  puts "Hello!"  # 块代码
end

## 更加直观的写法, 使用 &
def twice(&block)
  yield
  yield
end

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

twice() do
  puts "Hello!"
end

twice do
  puts "Hello!"
end

twice { puts "Hello!" }

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

foo bar do
  something
end

## The above is the same as
foo(bar) do
  something
end

foo bar { something } # same as : foo(bar { something })

## 多个参数 , 实际传入的参数入量可以少于yield后面的参数数量
def many
  yield 1, 2, 3
end

many do |x, y, z|
  puts x + y + z
end

## 单参数调用简写
method do |argument|
  argument.some_method
end

## 同上
method &.some_method
method &.some_method(arg1, arg2)

## 操作符也行。。。
method &.+(2)
method &.[index]

## 类型限制
def transform_int(start : Int32, &block : Int32 -> Int32)
  result = yield start
  result * 2
end

transform_int(3) { |x| x + 2 } #=> 10
transform_int(3) { |x| "foo" } # Error: expected block to return Int32, not String

## break ...略
## next ...略
## with...yield  ...略

## 块参数展开, 块代码参数可以被小括号展开
array = [{1, "one"}, {2, "two"}]
array.each do |(number, word)|
  puts "#{number}: #{word}"
end

## 上面的等价代码
array = [{1, "one"}, {2, "two"}]
array.each do |arg|
  number = arg[0]
  word = arg[1]
  puts "#{number}: #{word}"
end

捕获(生成)块

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

def int_to_int(&block : Int32 -> Int32)
  block
end

proc = int_to_int { |x| x + 1 }
proc.call(1) #=> 2

## 不指定返回类型,将没有返回
def some_proc(&block : Int32 ->)
  block
end

proc = some_proc { |x| x + 1 }
proc.call(1) # void

## 使用下划线 返回任意类型值
def some_proc(&block : Int32 -> _)
  block
end

proc = some_proc { |x| x + 1 }
proc.call(1) # 2

proc = some_proc { |x| x.to_s }
proc.call(1) # "1"

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

Proc字面量

def some_proc(&block : Int32 -> Int32)
  block
end

x = 0
proc = ->(i : Int32) { x += i }  # 字面量
proc = some_proc(&proc)
proc.call(1)  #=> 1
proc.call(10) #=> 11
x #=> 11

## 进程也可以从现有的方法创建
def add(x, y)
  x + y
end

adder = ->add(Int32, Int32)
adder.call(1, 2) #=> 3

块的转发执行

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

## 使用yield进行转发
def foo
  yield 1
end

def wrap_foo
  puts "Before foo"
  foo do |x|
    yield x
  end
  puts "After foo"
end

wrap_foo do |i|
  puts i
end

## 使用闭包转发, 效率会降低
def foo
  yield 1
end

def wrap_foo(&block : Int32 -> _)
  puts "Before foo"
  foo(&block)
  puts "After foo"
end

wrap_foo do |i|
  puts i
end

闭包

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

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

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

def counter
  x = 0
  ->{ x += 1; x }
end

proc = counter
proc.call #=> 1
proc.call #=> 2
## 上面的代码, x被proc字面量捕获并放置于堆中作为proc的上下文。一般情况下本地变量被放入栈中 并且在方法结束时被释放。

别名

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

alias PInt32 = Pointer(Int32)
ptr = PInt32.malloc(1) # : Pointer(Int32)

#
alias RecArray = Array(Int32) | Array(RecArray)

ary = [] of RecArray
ary.push [1, 2, 3]
ary.push ary
ary #=> [[1, 2, 3], [...]]