La ricorsione è una caratteristica in sé e per sé?


116

... o è solo una pratica?

Lo chiedo a causa di una discussione con il mio professore: ho perso il merito di chiamare una funzione in modo ricorsivo sulla base del fatto che non abbiamo trattato la ricorsione in classe, e la mia argomentazione è che l'abbiamo appresa implicitamente tramite l'apprendimento returne i metodi.

Lo chiedo qui perché sospetto che qualcuno abbia una risposta definitiva.

Ad esempio, qual è la differenza tra i seguenti due metodi:

public static void a() {
    return a();
    }

public static void b() {
    return a();
    }

Oltre a " acontinua per sempre" (nel programma vero e proprio viene utilizzato correttamente per richiamare nuovamente un utente quando viene fornito un input non valido), c'è qualche differenza fondamentale tra ae b? Per un compilatore non ottimizzato, come vengono gestiti in modo diverso?

Alla fine si tratta di capire se imparando return a()da bquello abbiamo imparato anche return a()da a. Lo abbiamo fatto?


24
Ottimo dibattito. Mi chiedo se l'hai spiegato così al tuo professore. Se lo hai fatto, penso che dovrebbe darti il ​​tuo credito perduto.
Michael Yaworski

57
La ricorsione non è nemmeno un concetto esclusivo dell'informatica. La funzione di Fibonacci, l'operatore fattoriale e molte altre cose dalla matematica (e possibilmente altri campi) sono (o almeno possono essere) espresse in modo ricorsivo. Il professore chiede che anche tu ignori queste cose?
Theodoros Chatzigiannakis

34
Il professore dovrebbe invece dargli credito extra, per aver escogitato un modo elegante per risolvere un problema, o per pensare fuori dagli schemi.

11
Qual era l'incarico? Questo è un problema su cui mi sono spesso chiesto, quando invii un incarico di programmazione, cosa viene contrassegnato, la tua capacità di risolvere un problema o la tua capacità di usare ciò che hai imparato. Questi due non sono necessariamente la stessa cosa.
Leon

35
FWIW, chiedere l'input finché non è corretto non è un buon posto per usare la ricorsione, è troppo facile sovraccaricare lo stack. Per questo caso particolare, sarebbe meglio usare qualcosa di simile a() { do { good = prompt(); } while (!good); }.
Kevin,

Risposte:


113

Per rispondere alla tua domanda specifica: No, dal punto di vista dell'apprendimento di una lingua, la ricorsione non è una caratteristica. Se il tuo professore ti ha davvero tolto i voti per aver usato una "caratteristica" che non aveva ancora insegnato, era sbagliato.

Leggendo tra le righe, una possibilità è che usando la ricorsione, hai evitato di usare una caratteristica che avrebbe dovuto essere un risultato di apprendimento per il suo corso. Ad esempio, forse non hai usato affatto l'iterazione, o forse hai usato solo i forloop invece di usare sia fore while. È normale che un incarico miri a testare la tua capacità di fare determinate cose e, se eviti di farle, il tuo professore semplicemente non può assegnarti i voti riservati per quella funzione. Tuttavia, se questa fosse davvero la causa dei tuoi voti persi, il professore dovrebbe prenderla come una sua esperienza di apprendimento - se la dimostrazione di determinati risultati di apprendimento è uno dei criteri per un compito, ciò dovrebbe essere spiegato chiaramente agli studenti .

Detto questo, sono d'accordo con la maggior parte degli altri commenti e risposte sul fatto che l'iterazione è una scelta migliore rispetto alla ricorsione qui. Ci sono un paio di ragioni, e mentre altre persone le hanno toccate in una certa misura, non sono sicuro che abbiano spiegato completamente il pensiero dietro di loro.

Stack Overflow

Quello più ovvio è che rischi di ricevere un errore di overflow dello stack. Realisticamente, è molto improbabile che il metodo che hai scritto porti a uno, poiché un utente dovrebbe fornire un input errato molte volte per attivare effettivamente un overflow dello stack.

Tuttavia, una cosa da tenere a mente è che non solo il metodo stesso, ma altri metodi superiori o inferiori nella catena di chiamate saranno nello stack. Per questo motivo, inghiottire casualmente lo spazio disponibile nello stack è una cosa piuttosto scortese da fare per qualsiasi metodo. Nessuno vuole doversi preoccupare costantemente dello spazio libero nello stack ogni volta che scrive codice a causa del rischio che altro codice possa averne usato inutilmente molto.

Questo fa parte di un principio più generale nella progettazione del software chiamato astrazione. In sostanza, quando chiami DoThing(), tutto ciò di cui dovresti preoccuparti è che la cosa sia fatta. Non dovresti preoccuparti dei dettagli di implementazione di come è fatto. Ma un uso avido dello stack infrange questo principio, perché ogni bit di codice deve preoccuparsi di quanto stack può tranquillamente presumere di avergli lasciato dal codice altrove nella catena di chiamate.

leggibilità

L'altro motivo è la leggibilità. L'ideale a cui il codice dovrebbe aspirare è di essere un documento leggibile dall'uomo, in cui ogni riga descrive semplicemente cosa sta facendo. Adotta questi due approcci:

private int getInput() {
    int input;
    do {
        input = promptForInput();
    } while (!inputIsValid(input))
    return input;
}

contro

private int getInput() {
    int input = promptForInput();
    if(inputIsValid(input)) {
        return input;
    }
    return getInput();
}

Sì, funzionano entrambi e sì, sono entrambi abbastanza facili da capire. Ma come potrebbero essere descritti i due approcci in inglese? Penso che sarebbe qualcosa del tipo:

Chiederò l'input finché l'input non sarà valido, quindi lo restituirò

contro

Chiederò l'input, quindi se l'input è valido lo restituirò, altrimenti ottengo l'input e restituisco il risultato di quello invece

Forse puoi pensare a una formulazione leggermente meno goffa per quest'ultimo, ma penso che troverai sempre che il primo sarà una descrizione più accurata, concettualmente, di ciò che stai effettivamente cercando di fare. Questo non vuol dire che la ricorsione sia sempre meno leggibile. Per le situazioni in cui brilla, come l'attraversamento degli alberi, potresti fare lo stesso tipo di analisi fianco a fianco tra la ricorsione e un altro approccio e quasi sicuramente troverai che la ricorsione fornisce un codice che è più chiaramente auto-descrittivo, riga per riga.

In isolamento, entrambi sono piccoli punti. È molto improbabile che ciò porti davvero a un overflow dello stack e il guadagno in leggibilità è minore. Ma qualsiasi programma sarà una raccolta di molte di queste piccole decisioni, quindi anche se isolatamente non contano molto, è importante imparare i principi che stanno dietro a farle bene.


8
Puoi espandere la tua affermazione che la ricorsione non è una caratteristica? Ho sostenuto nella mia risposta che lo è, perché non tutti i compilatori lo supportano necessariamente.
Harry Johnston,

5
Non tutti i linguaggi supportano necessariamente la ricorsione, quindi non è necessariamente solo una questione di scegliere il compilatore giusto, ma hai ragione a dire che "caratteristica" è una descrizione intrinsecamente ambigua, quindi abbastanza corretta. Il tuo secondo punto è giusto anche dal punto di vista di qualcuno che impara a programmare (come è ora normale) senza avere prima alcuna esperienza nella programmazione del codice macchina. :-)
Harry Johnston,

2
Notare che il problema di "leggibilità" è un problema di sintassi. Non c'è nulla di intrinsecamente "illeggibile" nella ricorsione. In effetti, l'induzione è il modo più semplice per esprimere strutture di dati induttive, come loop, elenchi e sequenze e così via. E la maggior parte delle strutture dati sono induttive.
nomen

6
Penso che tu abbia impilato il mazzo con le tue parole. Hai descritto la versione iterativa dal punto di vista funzionale e viceversa. Penso che una descrizione più corretta riga per riga di entrambi sarebbe "Chiederò input. Se l'input non è valido, continuerò a ripetere il prompt finché non ricevo un input valido. Poi lo restituirò. " vs "Chiederò un input. Se l'input è valido, lo restituirò. Altrimenti restituirò il risultato di un riporto. " (I miei figli hanno capito il concetto funzionale di un rinnovo quando erano all'asilo, quindi penso che qui sia un legittimo riassunto in inglese del concetto ricorsivo.)
pjs

2
@HarryJohnston La mancanza del supporto per la ricorsione sarebbe un'eccezione alle funzionalità esistenti piuttosto che la mancanza di una nuova funzionalità. In particolare, nel contesto di questa domanda, una "nuova caratteristica" significa "un comportamento utile che non abbiamo ancora insegnato esiste", il che non è vero per la ricorsione perché è un'estensione logica delle caratteristiche che sono state insegnate (vale a dire, che le procedure contengono istruzioni e chiamate di procedura sono istruzioni). È come se il professore insegnasse a uno studente l'addizione e poi lo rimproverasse per aver aggiunto lo stesso valore più di una volta perché "non abbiamo coperto la moltiplicazione".
nmclean

48

Per rispondere alla domanda letterale, piuttosto che alla meta-domanda: la ricorsione è una caratteristica, nel senso che non tutti i compilatori e / o linguaggi lo consentono necessariamente. In pratica, ci si aspetta da tutti i compilatori moderni (ordinari) e certamente da tutti i compilatori Java! - ma non è universalmente vero.

Come esempio artificioso del motivo per cui la ricorsione potrebbe non essere supportata, si consideri un compilatore che memorizza l'indirizzo di ritorno per una funzione in una posizione statica; questo potrebbe essere il caso, ad esempio, di un compilatore per un microprocessore che non dispone di uno stack.

Per un tale compilatore, quando chiami una funzione come questa

a();

è implementato come

move the address of label 1 to variable return_from_a
jump to label function_a
label 1

e la definizione di a (),

function a()
{
   var1 = 5;
   return;
}

è implementato come

label function_a
move 5 to variable var1
jump to the address stored in variable return_from_a

Si spera che il problema quando si tenta di chiamare in a()modo ricorsivo in un tale compilatore sia ovvio; il compilatore non sa più come tornare dalla chiamata esterna, perché l'indirizzo del mittente è stato sovrascritto.

Per il compilatore che ho effettivamente usato (fine anni '70 o inizio anni '80, credo) senza supporto per la ricorsione il problema era leggermente più sottile di questo: l'indirizzo di ritorno sarebbe stato memorizzato nello stack, proprio come nei compilatori moderni, ma le variabili locali non lo erano 't. (Teoricamente questo dovrebbe significare che la ricorsione era possibile per funzioni senza variabili locali non statiche, ma non ricordo se il compilatore lo supportava esplicitamente o meno. Potrebbe aver avuto bisogno di variabili locali implicite per qualche motivo.)

Guardando avanti, posso immaginare scenari specializzati - forse sistemi fortemente paralleli - in cui non dover fornire uno stack per ogni thread potrebbe essere vantaggioso, e dove quindi la ricorsione è consentita solo se il compilatore può refactoring in un ciclo. (Ovviamente i compilatori primitivi di cui ho discusso sopra non erano in grado di svolgere compiti complicati come il refactoring del codice.)


Ad esempio, il preprocessore C non supporta la ricorsione nelle macro. Le definizioni delle macro si comportano in modo simile alle funzioni, ma non è possibile chiamarle in modo ricorsivo.
sth

7
Il tuo "esempio artificioso" non è poi così artificioso: lo standard Fortran 77 non permetteva alle funzioni di chiamarsi ricorsivamente - il motivo è più o meno quello che descrivi. (Credo che l'indirizzo a cui saltare quando la funzione è stata eseguita fosse memorizzato alla fine del codice della funzione stesso, o qualcosa di equivalente a questa disposizione.) Vedi qui per un po 'a riguardo.
alexis

5
I linguaggi shader o GPGPU (ad esempio GLSL, Cg, OpenCL C) non supportano la ricorsione, ad esempio. Pertanto, l'argomento "non tutte le lingue lo supportano" è certamente valido. La ricorsione presume l'equivalente di uno stack (non deve essere necessariamente uno stack , ma deve esserci un mezzo per memorizzare in qualche modo gli indirizzi di ritorno ei frame di funzione ).
Damon

Un compilatore Fortran su cui ho lavorato un po 'all'inizio degli anni '70 non aveva uno stack di chiamate. Ogni subroutine o funzione aveva aree di memoria statica per l'indirizzo di ritorno, i parametri e le proprie variabili.
Patricia Shanahan

2
Anche alcune versioni di Turbo Pascal disabilitavano la ricorsione per impostazione predefinita e dovevi impostare una direttiva del compilatore per abilitarla.
dan04

17

L'insegnante vuole sapere se hai studiato o no. Apparentemente non hai risolto il problema nel modo in cui ti ha insegnato ( il buon modo ; iterazione), e quindi, considera che non l'hai fatto. Sono a favore di soluzioni creative ma in questo caso devo essere d'accordo con il tuo insegnante per un motivo diverso:
se l'utente fornisce un input non valido troppe volte (ad esempio tenendo premuto Invio), avrai un'eccezione di overflow dello stack e il tuo la soluzione andrà in crash. Inoltre, la soluzione iterativa è più efficiente e più facile da mantenere. Penso che questo sia il motivo per cui il tuo insegnante avrebbe dovuto darti.


2
Non ci è stato detto di svolgere questo compito in alcun modo specifico; e abbiamo imparato a conoscere i metodi, non solo l'iterazione. Inoltre, lascerei quale è più facile da leggere in base alle preferenze personali: ho scelto ciò che mi sembrava buono. L'errore SO è nuovo per me, sebbene l'idea che la ricorsione sia una caratteristica in sé e per sé non sembra ancora fondata.

3
"Lascerei quale è più facile da leggere alle preferenze personali". Concordato. La ricorsione non è una funzionalità Java. Questi sono.
mike

2
@Vality: eliminazione della chiamata di coda? Alcune JVM potrebbero farlo, ma tieni presente che deve anche mantenere una traccia dello stack per le eccezioni. Se consente l'eliminazione delle chiamate di coda, la traccia dello stack, generata in modo ingenuo, potrebbe diventare non valida, quindi alcune JVM non eseguono TCE per questo motivo.
icktoofay

5
In ogni caso, fare affidamento sull'ottimizzazione per rendere il codice non funzionante meno danneggiato, è una cattiva forma.
cHao

7
+1, vedi che in Ubuntu recentemente la schermata di accesso è stata interrotta quando l'utente ha premuto continuamente il pulsante Invio, lo stesso è successo per XBox
Sebastian

13

Dedurre punti perché "non abbiamo trattato la ricorsione in classe" è orribile. Se hai imparato a chiamare la funzione A che chiama la funzione B che chiama la funzione C che ritorna a B che ritorna ad A che ritorna al chiamante, e l'insegnante non ti ha detto esplicitamente che queste devono essere funzioni diverse (che sarebbe il caso delle vecchie versioni di FORTRAN, ad esempio), non c'è motivo per cui A, B e C non possano essere tutti la stessa funzione.

D'altra parte, dovremmo vedere il codice effettivo per decidere se nel tuo caso particolare usare la ricorsione è davvero la cosa giusta da fare. Non ci sono molti dettagli, ma sembra sbagliato.


10

Ci sono molti punti di vista da considerare riguardo alla domanda specifica che hai posto, ma quello che posso dire è che dal punto di vista dell'apprendimento di una lingua, la ricorsione non è una caratteristica a sé stante. Se il tuo professore ti ha davvero cancellato i voti per aver usato una "caratteristica" che non aveva ancora insegnato, era sbagliato ma come ho detto, ci sono altri punti di vista da considerare qui che in realtà fanno sì che il professore abbia ragione quando deduce punti.

Da quello che posso dedurre dalla tua domanda, l'utilizzo di una funzione ricorsiva per chiedere input in caso di errore di input non è una buona pratica poiché ogni chiamata di funzioni ricorsive viene inserita nello stack. Poiché questa ricorsione è guidata dall'input dell'utente, è possibile avere una funzione ricorsiva infinita e quindi risultante in uno StackOverflow.

Non c'è differenza tra questi 2 esempi che hai menzionato nella tua domanda nel senso di ciò che fanno (ma differiscono in altri modi) - In entrambi i casi, un indirizzo di ritorno e tutte le informazioni sul metodo vengono caricati nello stack. In un caso di ricorsione, l'indirizzo di ritorno è semplicemente la riga subito dopo la chiamata del metodo (ovviamente non è esattamente quello che vedi nel codice stesso, ma piuttosto nel codice creato dal compilatore). In Java, C e Python, la ricorsione è piuttosto costosa rispetto all'iterazione (in generale) perché richiede l'allocazione di un nuovo stack frame. Per non parlare del fatto che puoi ottenere un'eccezione di overflow dello stack se l'input non è valido troppe volte.

Credo che il professore abbia dedotto punti poiché la ricorsione è considerata un argomento a sé stante ed è improbabile che qualcuno senza esperienza di programmazione pensi alla ricorsione. (Ovviamente non significa che non lo faranno, ma è improbabile).

IMHO, penso che il professore abbia ragione deducendoti i punti. Avresti potuto facilmente prendere la parte di convalida su un metodo diverso e usarlo in questo modo:

public bool foo() 
{
  validInput = GetInput();
  while(!validInput)
  {
    MessageBox.Show("Wrong Input, please try again!");
    validInput = GetInput();
  }
  return hasWon(x, y, piece);
}

Se quello che hai fatto può davvero essere risolto in quel modo, allora quello che hai fatto è stata una cattiva pratica e dovrebbe essere evitato.


Lo scopo del metodo stesso è convalidare l'input, quindi chiamare e restituire il risultato di un altro metodo (ecco perché restituisce se stesso). Per essere precisi, controlla se la mossa in un gioco Tic-Tac-Toe è valida, quindi ritorna hasWon(x, y, piece)(per controllare solo la riga e la colonna interessate).

potresti facilmente prendere SOLO la parte di convalida e metterla in un altro metodo chiamato "GetInput" per esempio e poi usarlo come ho scritto nella mia risposta. Ho modificato la mia risposta con come dovrebbe apparire. Ovviamente puoi fare in modo che GetInput restituisca un tipo che contiene le informazioni di cui hai bisogno.
Yonatan Nir

1
Yonatan Nir: Quand'è che la ricorsione è stata una cattiva pratica? Forse JVM esploderà perché la VM Hotspot non può essere ottimizzata a causa della sicurezza del codice byte e cose del genere sarebbero un buon argomento. In che modo il tuo codice è diverso da quello che utilizza un approccio diverso?

1
La ricorsione non è sempre una cattiva pratica, ma se può essere evitata e mantenere il codice pulito e di facile manutenzione, dovrebbe essere evitata. In Java, C e Python, la ricorsione è piuttosto costosa rispetto all'iterazione (in generale) perché richiede l'allocazione di un nuovo stack frame. In alcuni compilatori C, è possibile utilizzare un flag del compilatore per eliminare questo sovraccarico, che trasforma alcuni tipi di ricorsione (in realtà, alcuni tipi di chiamate di coda) in salti invece che in chiamate di funzione.
Yonatan Nir

1
Non è chiaro, ma se sostituisci un ciclo con un numero indefinito di iterazioni con la ricorsione, allora non va bene. Java non garantisce l'ottimizzazione delle chiamate di coda, quindi potresti facilmente esaurire lo spazio dello stack. In Java, non utilizzare la ricorsione, a meno che non sia garantito un numero limitato di iterazioni (solitamente logaritmico rispetto alla dimensione totale dei dati).
hyde

6

Forse il tuo professore non l'ha ancora insegnato, ma sembra che tu sia pronto per imparare i vantaggi e gli svantaggi della ricorsione.

Il vantaggio principale della ricorsione è che gli algoritmi ricorsivi sono spesso molto più facili e veloci da scrivere.

Il principale svantaggio della ricorsione è che gli algoritmi ricorsivi possono causare overflow dello stack, poiché ogni livello di ricorsione richiede l'aggiunta di uno stack frame aggiuntivo allo stack.

Per il codice di produzione, dove il ridimensionamento può comportare molti più livelli di ricorsione in produzione rispetto agli unit test del programmatore, lo svantaggio di solito supera il vantaggio e il codice ricorsivo viene spesso evitato quando pratico.


1
Qualsiasi algoritmo ricorsivo potenzialmente rischioso può sempre essere riscritto banalmente per utilizzare uno stack esplicito: lo stack di chiamate è, dopo tutto, solo uno stack. In questo caso, se si riscrive la soluzione per usare uno stack, sembrerebbe ridicolo - un'ulteriore prova che la risposta ricorsiva non è molto buona.
Aaronaught

1
Se gli stack overflow rappresentano un problema, è necessario utilizzare un linguaggio / runtime che supporti l'ottimizzazione della chiamata di coda, come .NET 4.0 o qualsiasi linguaggio di programmazione funzionale
Sebastian

Non tutte le ricorsioni sono chiamate di coda.
Warren Dew

6

Per quanto riguarda la domanda specifica, è la ricorsione una caratteristica, sono propenso a dire di sì, ma dopo aver reinterpretato la domanda. Esistono scelte progettuali comuni di linguaggi e compilatori che rendono possibile la ricorsione, ed esistono linguaggi completi di Turing che non consentono affatto la ricorsione . In altre parole, la ricorsione è un'abilità abilitata da determinate scelte nella progettazione del linguaggio / del compilatore.

  • Il supporto di funzioni di prima classe rende possibile la ricorsione con presupposti minimi; vedi la scrittura di loop in Unlambda per un esempio, o questa espressione ottusa di Python che non contiene auto-riferimenti, loop o assegnazioni:

    >>> map((lambda x: lambda f: x(lambda g: f(lambda v: g(g)(v))))(
    ...   lambda c: c(c))(lambda R: lambda n: 1 if n < 2 else n * R(n - 1)),
    ...   xrange(10))
    [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
    
  • I linguaggi / compilatori che utilizzano l' associazione tardiva o che definiscono le dichiarazioni anticipate rendono possibile la ricorsione. Ad esempio, mentre Python consente il codice seguente, questa è una scelta di progettazione (associazione tardiva), non un requisito per un sistema completo di Turing . Le funzioni reciprocamente ricorsive spesso dipendono dal supporto per le dichiarazioni anticipate.

    factorial = lambda n: 1 if n < 2 else n * factorial(n-1)
    
  • I linguaggi tipizzati staticamente che consentono tipi definiti in modo ricorsivo contribuiscono ad abilitare la ricorsione. Guarda questa implementazione di Y Combinator in Go . Senza tipi definiti in modo ricorsivo, sarebbe ancora possibile utilizzare la ricorsione in Go, ma credo che il combinatore Y in particolare sarebbe impossibile.


1
Questo mi ha fatto esplodere la testa, soprattutto l'Unlambda +1
John Powell

I combinatori a virgola fissa sono difficili. Quando ho deciso di imparare la programmazione funzionale, mi sono costretto a studiare il combinatore Y fino a quando non l'ho capito, per poi applicarlo per scrivere altre funzioni utili. Mi ci è voluto un po ', ma ne è valsa la pena.
wberry

5

Da quello che posso dedurre dalla tua domanda, utilizzare una funzione ricorsiva per chiedere input in caso di errore di input non è una buona pratica. Perché?

Perché ogni chiamata di funzioni ricorsive viene inserita nello stack. Poiché questa ricorsione è guidata dall'input dell'utente, è possibile avere una funzione ricorsiva infinita e quindi risultante in uno StackOverflow :-p

Avere un ciclo non ricorsivo per farlo è la strada da percorrere.


La maggior parte del metodo in questione e lo scopo del metodo stesso è convalidare l'input attraverso vari controlli. Il processo ricomincia dall'inizio se l'input non è valido fino a quando l'input non è corretto (come indicato).

4
@fay Ma se l'input non è valido troppe volte, otterrai un'eccezione StackOverflowError. La ricorsione è più elegante, ma ai miei occhi, di solito è più un problema rispetto a un ciclo normale (a causa degli stack).
Michael Yaworski

1
Questo è un punto interessante e positivo, quindi. Non avevo considerato quell'errore. Tuttavia, lo stesso effetto può essere ottenuto while(true)chiamando lo stesso metodo? Se è così, non direi che questo supporta alcuna differenza tra la ricorsione, buono a sapersi così com'è.

1
@fay while(true)è un ciclo infinito. A meno che tu non abbia una breakdichiarazione, non ne vedo il punto, a meno che tu non stia cercando di mandare in crash il tuo programma lol. Il punto è che se chiami lo stesso metodo (cioè la ricorsione), a volte ti darà un StackOverflowError , ma se usi un ciclo whileo for, non lo farà. Il problema semplicemente non esiste con un ciclo regolare. Forse ti ho frainteso, ma la mia risposta è no.
Michael Yaworski

4
Questo a me onestamente sembra probabilmente il vero motivo per cui il professore ha tolto i voti =) Potrebbe non averlo spiegato molto bene, ma è una lamentela valida dire che lo stavi usando in un modo che sarebbe considerato uno stile molto povero se non lo fosse assolutamente imperfetto in un codice più serio.
Comandante Coriander Salamander

3

La ricorsione è un concetto di programmazione , una funzionalità (come l'iterazione) e una pratica . Come puoi vedere dal link, c'è un ampio dominio di ricerca dedicato all'argomento. Forse non abbiamo bisogno di approfondire l'argomento per comprendere questi punti.

Ricorsione come caratteristica

In parole povere, Java lo supporta implicitamente, perché consente a un metodo (che è fondamentalmente una funzione speciale) di avere "conoscenza" di se stesso e degli altri metodi che compongono la classe a cui appartiene. Considera un linguaggio in cui non è così: saresti in grado di scrivere il corpo di quel metodo a, ma non potresti includere una chiamata al asuo interno. L'unica soluzione sarebbe usare l'iterazione per ottenere lo stesso risultato. In un linguaggio del genere, dovresti fare una distinzione tra le funzioni consapevoli della propria esistenza (utilizzando uno specifico token di sintassi) e quelle che non lo fanno! In realtà, un intero gruppo di lingue fa quella distinzione (vedi Lisp e lambda ) per chiamarsi ricorsivamente (di nuovo, con una sintassi dedicata). famiglie ML per esempio). È interessante notare che Perl consente persino funzioni anonime (le cosiddette

nessuna ricorsione?

Per i linguaggi che non supportano nemmeno la possibilità di ricorsione, c'è spesso un'altra soluzione, nella forma del combinatore a virgola fissa , ma richiede comunque che il linguaggio supporti funzioni come i cosiddetti oggetti di prima classe (cioè oggetti che possono essere manipolato all'interno della lingua stessa).

Ricorsione come pratica

Avere quella funzione disponibile in una lingua non significa necessariamente che sia idiomatica. In Java 8 sono state incluse le espressioni lambda, quindi potrebbe diventare più facile adottare un approccio funzionale alla programmazione. Tuttavia, ci sono considerazioni pratiche:

  • la sintassi non è ancora molto adatta alla ricorsione
  • i compilatori potrebbero non essere in grado di rilevare tale pratica e ottimizzarla

La linea di fondo

Fortunatamente (o più precisamente, per facilità d'uso), Java consente ai metodi di essere consapevoli di se stessi per impostazione predefinita e quindi supporta la ricorsione, quindi questo non è davvero un problema pratico, ma rimane comunque uno teorico, e suppongo che il tuo insegnante voleva affrontarlo in modo specifico. Inoltre, alla luce della recente evoluzione della lingua, potrebbe trasformarsi in qualcosa di importante in futuro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.