Why does explicit return make a difference in a Proc?

Asked
Viewd14549

54
def foo
  f = Proc.new { return "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo" 
end

def bar
  b = Proc.new { "return from bar from inside proc" }
  b.call # control leaves bar here
  return "return from bar" 
end

puts foo # prints "return from foo from inside proc" 
puts bar # prints "return from bar" 

I thought the return keyword was optional in Ruby and that you are always returning whether you request it or not. Given that, I find it surprising that foo and bar have different output determined by the fact that foo contains an explicit return in Proc f.

Does anyone know why this is the case?

3 ответов

92

В Ruby есть три конструкции:

  1. блок не является объектом и создается с помощью { ... } или do ... end.
  2. proc - это объект Proc, созданный Proc.new или proc.
  3. лямбда - это Proc, созданное lambda (или proc в Ruby 1.8).

В Ruby есть три ключевых слова, которые откуда-то возвращаются:

  1. return завершает метод или лямбду, в которой он находится.
  2. next завершает блок, процедуру или лямбду, в которой он находится.
  3. break завершает метод, который привел к блоку или вызвал процедуру или лямбду, в которой он находится.

В лямбдах return ведет себя как next по любой причине. next и break названы так, как они есть, потому что они чаще всего используются с такими методами, как each, где завершение блока приведет к возобновлению итерации с помощью следующего элемента коллекции, а завершение each приведет к вы должны выйти из цикла.


Если вы используете return внутри определения foo, вы вернетесь из foo, даже если он находится внутри блока или процедуры. Чтобы вернуться из блока, вы можете вместо этого использовать ключевое слово next.
 def foo
  f = Proc.new { next "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo" 
end
puts foo # prints "return from foo"
 
  • Кстати, я думаю, что мы оба отлично поработали, ответив на другой вопрос :) почему, я думаю, только Матц знает, что многие вещи, связанные с закрытием, нарушают принцип наименьшего удивления.

    Sam Saffron17 сентября 2009, 09:33
  • note, in your example if next is omitted, the behavior remains.

    Sam Saffron17 сентября 2009, 09:17
  • Edited to also mention the behaviour of lambdas

    sepp2k17 сентября 2009, 08:49
8

Подумайте об этом так: Proc.new просто создает блок кода, который является частью вызывающей функции. proc / lambda создает анонимную функцию со специальными привязками. Небольшие примеры кода помогут:

 def foo
  f = Proc.new { return "return from foo from inside Proc.new" }
  f.call # control leaves foo here
  return "return from foo" 
end
 

эквивалентно

 def foo
  begin
    return "return from foo from inside begin/end" }
  end

  return "return from foo" 
end
 

поэтому ясно, что возврат будет просто возвращен из функции 'foo'

в отличие от:

 def foo
  f = proc { return "return from foo from inside proc" }
  f.call # control stasy in foo here
  return "return from foo" 
end
 

эквивалентен (игнорируя привязки, поскольку в этом примере не используется):

 def unonymous_proc
  return "return from foo from inside proc"
end

def foo
  unonymous_proc()
  return "return from foo" 
end
 

Который также явно не вернется из foo и вместо этого перейдет к следующему оператору.

14

Это семантика для Proc; это не обязательно семантика для всех блоков. Я согласен, это немного сбивает с толку. Это сделано для дополнительной гибкости (и, возможно, частично потому, что у Ruby нет спецификации, кроме ее реализации).

Поведение определяется в реализации Proc. Lambda ведут себя по-разному, поэтому, если вы хотите, чтобы ваши return не выходили из метода включения, используйте лямбды . Или опустите ключевое слово return из своего Proc.

Подробное исследование замыканий Rubys находится здесь . Это фантастическое разоблачение.

Итак:

 def foo   
  f = Proc.new {
    p2 = Proc.new { return "inner proc"};
    p2.call
    return "proc"
  }
  f.call
  return "foo"
end

def foo2
  result = Proc.new{"proc"}.call
  "foo2 (proc result is: #{result})"
end

def bar
  l = lambda { return "lambda" }
  result = l.call
  return "bar (lambda result is: #{result})"
end

puts foo
# inner proc
puts foo2
# foo (proc result is: proc) 
puts bar
# bar (lambda result is: lambda) 
 
  • “This is the semantics for Procs, it is not necessarily the semantics for all blocks.” It is the semantics for Proc instances created by Proc.new as well as all “normal” blocks (i.e. blocks that aren’t used together with the proc or lambda keywords).

    sepp2k17 сентября 2009, 08:51
  • True, I could add an example, but I think my example is complicated enough as it.

    Sam Saffron17 сентября 2009, 09:18