在 ruby 中,有三个关键字可以从一段代码中返回,分别是return
、next
、break
,今天主要研究一下 return。
在 ruby 中调用一个方法,默认的情况下,会一行一行依次执行方法中的代码,方法的值是最后一行的值。
return 可以改变这个方法一行一行执行的行为,当遇到 return,方法会直接返回(rescue、ensure 例外)。
def test
puts 'first puts'
return
puts 'next puts'
end
在上面这个例子中,puts 'next puts'
这行代码永远不会执行。同时我们可以显示指定返回值,默认为 nil。例如上面这个例子没有指定返回值,其返回值则为 nil。
代码胜千言,先看几个例子:
def test1
proc = Proc.new { return 10 }
puts 'puts here'
end
def test2
proc = Proc.new { return 10 }
proc.call
puts 'puts here'
end
def test3
return 10
puts 'puts here'
end
test4 = Proc.new { return 10 }
test4.call
在上面的例子中:
test1
方法,puts 'puts here'
能够执行,返回值为最后一行代码的返回值。
test2
方法,返回值为 10。
test3
方法的行为和test2
是等价的。
test4
在 call 调用的时候会报错,错误信息是:LocalJumpError: unexpected return
;
为什么会这样,其实test2
和test3
已经告诉我们答案了,在 proc 中使用 return 和在方法本身中使用 return 的效果是一样的。
而test4
的例子就相当于你直接执行return 10
,报的错误也同样是:LocalJumpError: unexpected return
LocalJumpError
这个错误其实很有意思,如果拦截这个错误,是依然可以拿到 return 的返回值的。
下面是我在 pry 中做的实验。
return 10
#=> LocalJumpError: unexpected return
_ex_
#=> <LocalJumpError: unexpected return>
_ex_.class
#=> LocalJumpError < StandardError
_ex_.exit_value
#=> 10
_ex_.reason
#=> :return
还是继续看例子:
test1 = -> { puts 'first puts'; return 10; puts 'next puts' }
test1.call
# first puts
#=> 10
def test2
la = -> { puts 'first puts'; return 10; puts 'next puts' }
la.call
puts 'puts here'
end
# first puts
# puts here
#=> nil
lambda 对象,跟 proc 对象不一样,在 lambda 中,return 是从 lambda 代码块中返回。在 proc 中,return 是从 proc 代码块所在的上下文环境中返回。
。。。。你只要说 Ruby 中 block 不是 function/lambda 就行了,哪来这么多混乱。在其它语言中 return 都是退出 function/lambda,只是 Ruby 多出了 block,它用的是花括号,像 lambda 但又不是,才产生了混淆
#10 楼 @cicholgricenchos 刚去试了下好像 each 表现的和 proc 一样,直接从代码块的所在的上下文环境中返回了,还真是有点小惊讶。。但是把 代码块变成 Proc 对象时大家确实应该是习惯用 lambda 的。
补充一个,大家知道如何将 proc 转为 lambda 吗,最近遇到了这个需求,解决如下:
def _convert_to_lambda(&block)
self.define_singleton_method(:_, &block)
return self.method(:_).to_proc
end
def double(callable_object)
callable_object.call * 2
end
puts double( lambda { return 10 } )
def another_double
p = Proc.new { return 10 }
result = p.call
return result * 2
end
puts another_double
大家考虑一下这个返回的是什么?分享一下思路。
Ruby 元编程这本书里有讲这个哦,还有一个就是 proc 和 lambda 对于传入的参数的处理不太一样。 proc 会帮我们处理一下参数,lambda 不会
proc:
proc = Proc.new {|a, b| a * b}
proc.call(1, 2, 3)
# => 2
lambda:
ld = lambda {|a, b| a * b}
ld.call(1, 2, 3)
# => ArgumentError: wrong number of arguments (3 for 2)
虽然自己写代码时大多数会使用 lambda,但 Ruby 内建的方法(比如迭代器)都是用的 proc。因为:
目前貌似没发现 Ruby 内建方法使用 lambda 的情况。
一个有意思的地方是,因为上面的原因,proc 被隐蔽地用在很多地方。如果有时候需要像 lambda 那样 return 的时候,可以用 next
代替。比如:
user.save.tap do |success|
next unless success
send_notification
write_log
end
@est 上面的帖子就是个例子,我是用在 tap 里面。目的嘛:
proc 被隐蔽地用在很多地方。如果有时候需要像 lambda 那样 return 的时候,可以用 next 代替。
proc 是代码片段(snippet),它里面的 return 就相当于结束调用它的 context 的执行。 lambda 是匿名方法(method),它里面的 return 就是结束该 method(自己)的执行。
说白了也是 block 与 lambda 的区别,lambda 本质上是函数,所以遇到 return 时会从 lambda 函数内返回,而 block 是一段能执行的代码,相当于你在函数中插入一段断码,当 block 中包含 return 语句时,导致整个函数就返回了
def func(array, &block)
array.each do |ele|
block.call(ele)
end
end
fruits = ["banana", "apple", "pear"]
func(fruits) do |f|
# return if f=="pear"
next if f=="pear"
puts "i like" + f.capitalize
end
请教各位,如果使用注释里的 return 的写法,就报 unexpected return (LocalJumpError)
, 怎么解释呢?
不好意思,我一开始对 proc 的上下文理解错了。这样就明白了
def func(array, &block)
v = 1
array.each do |ele|
block.call(ele)
end
end
fruits = ["banana", "apple", "pear"]
v = 2
func(fruits) do |f|
return (p v) if f=="pear"
# next if f=="pear"
puts "i like" + f.capitalize
end
=begin
i likeBanana
i likeApple
2
main.rb:22:in `block in <main>': unexpected return (LocalJumpError)
=end
我测试了一下,其实块中 return 语句会返回的就是定义这个块的地方。下面举例:
def test
proc = Proc.new { return 3 }
proc.call
puts "test finished"
end
在执行了 proc. call 之后,test 方法会 return 3。然而,这是因为 Proc.new 写在了 test 方法中而不是因为 proc.call 写在了 test 方法中。換言之,return 跳出的是 Proc.new 所在的方法,而非 proc.call 所在的方法。
为了证明这一点,我们可以尝试以下代码:
def test(&block)
block.call
end
p = proc { return 10 }
num = test(&p)
p num
上面这段代码的最后一行是不会执行到的。因为 p = proc { return 10 } 这行意味着 call 这个块时会从当前定义这个块的上下文中 return (定义这个块时不在任何 method 中) 。所以当这个块传给了 test 方法并在其中调用时,它直接就从最外围 return 了 (就是定义 proc 的上下文),导致这个程序也就结束了,p num 这行还没来的及执行。