Почему явный возврат имеет значение в Proc?

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" 

Я думал, что ключевое слово return не является обязательным в Ruby и что вы всегда return, запрашиваете ли вы его или нет. Учитывая это, я нахожу удивительным, что foo и bar имеют различный вывод, определяемый тем фактом, что foo содержит явный return в Proc f.

Кто-нибудь знает, почему это так?

Ответ 1

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

  • Блок не является объектом и создается {... } или do... end.
  • Проком является объект Proc, созданный Proc.new или Proc.
  • lambda - это Proc, созданный lambda (или Proc в Ruby 1.8).

Ruby имеет три ключевых слова, которые возвращаются от чего-то:

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

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


Если вы используете return внутри определения foo, вы вернетесь из foo, даже если он находится внутри блока или proc. Чтобы вернуться из блока, вы можете использовать ключевое слово 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"

Ответ 2

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

Поведение определено в реализации Proc. Lambda ведут себя по-другому, поэтому, если вы хотите, чтобы ваш return не вышел из из метода размещения, используйте lambdas. Или опустите ключевое слово 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) 

Ответ 3

Подумайте об этом так: 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 и не перейдет к следующему утверждению.