2014-04-30 12 views
10

Per qualche motivo, Ruby sembra funzionare meglio quando si trova di fronte alla ricorsione a sinistra. Per esempio:Ruby left vs right ricorsione

def left_recursive_factorial(number) 
    return 1 if number.zero? 
    left_recursive_factorial(number.pred) * number 
end 

def right_recursive_factorial(number) 
    return 1 if number.zero? 
    number * right_recursive_factorial(number.pred) 
end 

Quando chiamo questi metodi con un valore di oltre 9000() ottengo risultati diversi:

left_recursive_factorial(9001) 
    # => factorial of 9001 

right_recursive_factorial(9001) 
    # => SystemStackError: stack level too deep 
    # from (pry):6:in `right_recursive_factorial' 

non ho potuto trovare alcuna spiegazione per questo comportamento.

L'unica cosa che sembrava in qualche modo correlata era circa i parser LL() che hanno problemi con la ricorsione a sinistra e credo che si possa capovolgere, ma non ho scavato molto dentro.

Qualcuno potrebbe spiegare un po 'più in dettaglio ciò che causa la ricorsione destra e quella sinistra in modo diverso (in generale e in Ruby in particolare) e se si ha la possibilità di scegliere l'uno o l'altro perché lo si dovrebbe scegliere (e perché è stato lasciato scelto in Ruby)?

+0

Sembra che Ruby valuti il ​​lato destro di una moltiplicazione prima di sinistra, e quindi la versione di sinistra usa [ricorsione di coda] (https://en.wikipedia.org/wiki/Tail_call) in modo che non debba aggiungere nello stack. – clcto

+0

@clcto: non sembra un'eliminazione della coda. Per uno, la moltiplicazione è l'ultima operazione nella funzione, non la chiamata ricorsiva. Per un altro, continuerete a saltare lo stack se solo il numero fino a 9500. – Chuck

+4

@clcto Ruby valuta decisamente gli operandi e gli argomenti del metodo da sinistra a destra, non da destra a sinistra. Inoltre, l'ordine in cui vengono valutati gli operandi è irrilevante rispetto al fatto che qualcosa sia ricorsivo o meno. La moltiplicazione avviene necessariamente dopo la chiamata alla funzione (poiché non è possibile moltiplicare due numeri finché non si conoscono entrambi i numeri), quindi il metodo non è ricorsivo in coda, indipendentemente dal numero che viene valutato per primo. In ogni caso, l'interprete standard di Ruby non ottimizza la ricorsione della coda. – sepp2k

risposta

6

OK, io non sono un hacker YARV di alcun tipo, ma ecco la differenza come ho capito. Quando chiami un metodo, il mittente invia gli argomenti del metodo nello stack, quindi il metodo chiamato espelle i suoi argomenti e ne attiva il valore restituito. Con la chiamata ricorsiva, l'argomento number non è stato ancora inserito nello stack, quindi lo stack di ogni chiamata occupa uno spazio leggermente inferiore. Questo è il motivo per cui è possibile ottenere alcune ripetizioni in più rispetto a quella versione, ma non in modo drastico in più: si sta osservando una riduzione di alcune percentuali nell'utilizzo dello stack.

+0

Questo ha senso, ma speravo in una risposta un po 'più dettagliata. Ad esempio perché python e php sembrano essere indifferenti, mentre js mostra un comportamento simile. – ndn

+1

@ndn: dipende molto dall'implementazione del linguaggio. Ad esempio, Python ha un limite di ricorsione, che è un valore reale che l'interprete controlla e rifiuta le chiamate ricorsive se è stato raggiunto. Puoi cambiarlo con 'sys.setrecursionlimit()'. Nel caso di PHP, ho dovuto prendere un disassemblatore perché non avevo idea di come funzionasse. Non ci vuole l'approccio stack-machine per spingere gli argomenti e quindi chiamare il multiplo. Invece, in realtà dà gli operandi a '*' come operandi dell'opcode 'MUL', quindi l'unica differenza tra le funzioni è' MUL! 0, $ 2' contro 'MUL $ 2,! 0'. – Chuck

Problemi correlati