Почему явный возврат имеет значение в 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.

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


person uzo    schedule 16.09.2009    source источник


Ответы (3)


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"
person sepp2k    schedule 16.09.2009
comment
Отредактировано, чтобы также упомянуть поведение лямбда-выражений - person sepp2k; 17.09.2009
comment
обратите внимание, что в вашем примере, если next опущен, поведение остается. - person Sam Saffron; 17.09.2009
comment
кстати, я думаю, что мы оба проделали отличную работу, отвечая на другой вопрос :) что касается того, почему, я думаю, знает только Матц, многие вещи, связанные с замыканиями, нарушают принцип наименьшего удивления. - person Sam Saffron; 17.09.2009
comment
Спасибо за исчерпывающий ответ здесь. Действительно проясняет ситуацию. - person ghayes; 22.02.2013
comment
Спасибо за объяснение с хорошим примером - person Sasidaran; 18.11.2017

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

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

Глубокое исследование замыканий Ruby здесь. Это фантастическое разоблачение.

So:

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) 
person Sam Saffron    schedule 16.09.2009
comment
Это семантика для Procs, это не обязательно семантика для всех блоков. Это семантика для экземпляров Proc, созданных Proc.new, а также для всех обычных блоков (т. е. блоков, которые не используются вместе с ключевыми словами proc или lambda). - person sepp2k; 17.09.2009
comment
Правда, я мог бы добавить пример, но я думаю, что мой пример достаточно сложен. - person Sam Saffron; 17.09.2009
comment
по какой-то причине лямбды, преобразованные в блоки, ведут себя так, как будто они всегда были блоками: [1].map &lambda{return "lambda"} возвращается из функции. Это ошибка в JRuby? - person John Dvorak; 20.10.2013
comment
puts foo2 вернется foo2 (proc result is: proc) - person spyle; 30.09.2015

Подумайте об этом так: 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

поэтому ясно, что return просто вернется из функции '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 и вместо этого перейдет к следующему оператору.

person Vitaly Kushner    schedule 17.09.2009
comment
хотя это старый вопрос, обратите внимание, что существует разница между Ruby 1.8.7 и 1.9.3, где в последнем Kernel.proc ведет себя как Proc.new, а не как лямбда. - person froginvasion; 24.01.2013