Вызов метода Ruby рекурсивно изнутри связанного с ним блока. Любым другим путем?

Я придумал это:

def f x, &b
  yield x, b
end
f 4 do |i, b|
  p i
  f i - 1, &b if i > 0
end

Результат:

4
3
2
1
0

Есть ли другой способ?

Ответ 1

Это зависит от особенностей вашего реального кода, но, учитывая ваш пример, если вы назовете блок заранее, вы можете избежать ввода значения и блока в своей функции. Например:

def f(x, &b)
  yield x
end

foo = lambda do |i|
  p i
  f(i-1,&foo) if i > 0
end
f(4,&foo)

Однако я хотел бы найти более элегантное решение этой проблемы. Я подозреваю, что это будет хорошим применением Y combinator. Как только у меня будет что-то лучше для вас, я обновлю это сообщение.

Ответ 2

Блок может рекурсивно вызывать себя, если он хранится в переменной, доступной самому блоку. Например:

def f(x)
  block = lambda do |y|
    # some calculation on value, or simply yield to the block passed to f()
    yield y
    block.call(y - 1) if y > 0
  end
  block.call(x)
end

f(4) do |x|
  puts "Yielded block: #{x}"
end

В качестве альтернативы вы можете вернуть рекурсивный блок, привязанный к блоку вызывающих, и затем вызвать этот блок. Например:

def g
  block = lambda do |y|
    # some calculation on value, or simply yield to block passed to g()
    yield y
    block.call(y - 1) if y > 0
  end
end

printing_descender = g do |x|
  puts "Encapsulated block: #{x}"
end
printing_descender.call(4)

Выходы:

Yielded block: 4
Yielded block: 3
Yielded block: 2
Yielded block: 1
Yielded block: 0
Encapsulated block: 4
Encapsulated block: 3
Encapsulated block: 2
Encapsulated block: 1
Encapsulated block: 0

Ответ 3

def f(x, &b)
  b.call x
  f(x-1,&b) if x>0
end

f(4) do |x|
 p x
end

Ответ 4

Мэтт ответ хороший. Это также единственный способ реализовать немедленное возвращение из глубокого рекурсивного поиска. Обратите внимание, что возврат из блока фактически возвращается из вызывающей функции. разматывая все рекурсивные вызовы блоков одним махом.

Ответ 5

существует множество способов сделать это с помощью callcc или catch/throw (который всегда может вернуться из глубоких рекурсивных вызовов). здесь не-игра для гольфа, в которой используются локальные локаторы потоков

def f x, &b
  t = Thread.current
  t[:b] ||= b
  b ||= t[:b]
  b.call(x)
ensure
  t[:b] = nil
end

f 4 do |i|
  p i
  f i - 1 if i > 0
end