分享 透彻理解 Ruby 中的 return

mingyuan0715 · 2015年03月22日 · 最后由 2828qwk 回复于 2020年05月13日 · 14930 次阅读
本帖已被管理员设置为精华贴

在 ruby 中,有三个关键字可以从一段代码中返回,分别是returnnextbreak,今天主要研究一下 return。

方法中的 return

在 ruby 中调用一个方法,默认的情况下,会一行一行依次执行方法中的代码,方法的值是最后一行的值。

return 可以改变这个方法一行一行执行的行为,当遇到 return,方法会直接返回(rescue、ensure 例外)。

def test
  puts 'first puts'
  return
  puts 'next puts'
end

在上面这个例子中,puts 'next puts'这行代码永远不会执行。同时我们可以显示指定返回值,默认为 nil。例如上面这个例子没有指定返回值,其返回值则为 nil。

proc 中的 return

代码胜千言,先看几个例子:

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;

为什么会这样,其实test2test3已经告诉我们答案了,在 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

lambda 中的 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 代码块所在的上下文环境中返回。

支持,Proc 和 Lambda 这个细节以前没太注意

匿名 #3 2015年03月23日

good job :plus1:

👍 学习了,pry 中_ex_

:plus1: proc 和 lambda 中 return 的不同是很容易让人迷惑的。

ruby 元编程 里面有详细的说明

我之前看 proc 和 lambda 的区别没有特别明白,这样来解释感觉好容易明白

简单来说,基本上不用 proc,都是用 lambda 的。

。。。。你只要说 Ruby 中 block 不是 function/lambda 就行了,哪来这么多混乱。在其它语言中 return 都是退出 function/lambda,只是 Ruby 多出了 block,它用的是花括号,像 lambda 但又不是,才产生了混淆

#8 楼 @chiangdi 然而每次用 each 的时候都在用 proc : -)

#10 楼 @cicholgricenchos 刚去试了下好像 each 表现的和 proc 一样,直接从代码块的所在的上下文环境中返回了,还真是有点小惊讶。。但是把 代码块变成 Proc 对象时大家确实应该是习惯用 lambda 的。

赞,写的简单易懂,又复习了一遍

15 楼 已删除

有三个关键字可以从一段代码中返回,分别是 return、next、break

还有一个 yield,甚至直接抛异常,如果你喜欢的话。

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。因为:

  1. proc 中 return 直接作用于外层的 scope。这点在迭代器中很常用。
  2. proc 参数验证没 lambda 那么严格,这点也在迭代器中很常用。

目前貌似没发现 Ruby 内建方法使用 lambda 的情况。

一个有意思的地方是,因为上面的原因,proc 被隐蔽地用在很多地方。如果有时候需要像 lambda 那样 return 的时候,可以用 next 代替。比如:

user.save.tap do |success|
  next unless success

  send_notification
  write_log
end

#19 楼 @darkbaby123 呃。。。这个 next 是 Enumerator 特有的吧。。。。

巩固基础知识

还是要再看一遍元编程呐。

@est 实际证明对非 enumerator 的 block 也有效果。

#23 楼 @darkbaby123 求事实学习下。。。

#24 楼 @est

def some_function()
  yield 2
end

some_function() {|y| next y*y} # => 4

其实就是退出当前 block.

@est 上面的帖子就是个例子,我是用在 tap 里面。目的嘛:

proc 被隐蔽地用在很多地方。如果有时候需要像 lambda 那样 return 的时候,可以用 next 代替。

正在学习 ruby,mark 一下

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) , 怎么解释呢?

wwwicbd 回复

很明显你已经用了个 proc

mingyuan0715 回复

不好意思,我一开始对 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
lyfi2003 回复

请问有木有办法写成一句话呢?不占用额外的方法名

36 楼 已删除

我测试了一下,其实块中 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 这行还没来的及执行。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号