2012-01-04 5 views
5

Есть ли обходной путь для ошибок переполнения стека в рекурсивных функциях в Ruby?Есть ли обходной путь для ошибок «слишком высокий уровень стека» в рекурсивных подпрограммах?

Скажем, к примеру, у меня есть этот блок:

def countUpTo(current, final) 
    puts current 
    return nil if current == final 
    countUpTo(current+1, final) 
end 

, если я называю countUpTo(1, 10000), я получаю сообщение об ошибке: stack level too deep (SystemStackError).

Похоже, он разбивается на 8187. Есть ли какая-то функция, которую я могу назвать, говоря Ruby, чтобы игнорировать размер стеков или способ увеличить максимальный размер стека?

+5

Не делайте этого. Если вы намеренно повторяете 10 000 раз, вы делаете это ужасно неправильно и злоупотребляете рекурсией. – meagar

+2

Реализации Ruby не обязательно устраняют удаление хвоста, поэтому вы полагаетесь на использование размера стека C. Одна из возможностей заключается в том, что вы можете переписать свою функцию как итеративную. – birryree

+0

Во-первых, мой собственный опыт работы с Ruby заключается в том, что он не особенно хорош в рекурсии, поскольку он создает ошибки, подобные этому, довольно легко и медленнее (чем вам хотелось бы). Кроме того, чтобы получить лучшую производительность в этой области, вам нужно скомпилировать Ruby с определенным набором констант, но я не нашел, что это очень помогло. Другими словами, напишите свою функцию по-другому, используя обычные методы Ruby, такие как 'times',' upto' и т. Д. @meagar, если вы не знаете, в чем цель, я не думаю, что вы можете сделать это утверждение. Я написал методы в Haskell, которые решают, что количество раз не проблема, и это дежурство. – iain

ответ

2

Если вы используете YARV (реализация на основе C в Ruby 1.9), вы можете сказать на Ruby VM, чтобы включить оптимизацию хвостового вызова на:

RubyVM::InstructionSequence.compile_option = { 
    :tailcall_optimization => true, 
    :trace_instruction => false 
} 

def countUpTo(current, final) 
    puts current 
    return nil if current == final 
    countUpTo(current+1, final) 
end 

countUpTo(1, 10_000) 
+0

Как я писал выше, я нашел, что это мало влияет, но YMMV. – iain

+0

После того, как вы установили compile_option, вы можете использовать 'new' или' compile' методы в последовательности команд, например: 'RubyVM :: InstructionSequence.new ('def meth_name (b, c); d = b + c; puts" # {b} + # {c} = # {d}, теперь выполняя # {b + 1} + # {c} ... "; meth_name (b + 1, c) end '). eval'. Более общий способ сделать это показан [здесь] (https://gist.github.com/rogerleite/5101751). –

+0

Или, если вы предпочитаете не устанавливать параметры для всего, вы можете передать параметры в compile/new, например. 'RubyVM :: InstructionSequence.new ('def meth_name (b, c); d = b + c; puts" # {b} + # {c} = # {d}. Теперь выполняется # {b + 1} + # {c} ... "; meth_name (b + 1, c) end ', nil, nil, nil, tailcall_optimization: true, trace_instruction: false) .eval'. Подробнее см. [:: RubyVM :: InstructionSequence] (http://www.ruby-doc.org/core-2.0.0/RubyVM/InstructionSequence.html). –

2

Вы можете переписать фрагмент не рекурсивный:

# 'count_up_to' would be a more "Ruby" name ;-) 
def countUpTo(current, final) 
    (current..final).each { |i| puts i } 
end 

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

НТН

+0

В общем, неограниченная рекурсия плохая. Большинство рекурсий можно переписать так, чтобы они были итеративными, как показал Павлинг. – nmjohn

+0

Как это было достойно нисходящего ?! }: - [ – Pavling

+0

@Pavling: Потому что вы говорите «не делайте этого» (не делайте рекурсии), не давая веской причины не делать этого. –