Differenze tra i debugger del linguaggio imperativo e del linguaggio funzionale

6

Fino ad ora ho sempre lavorato con linguaggi imperativi come Pascal, C, C ++, Java in un ambiente di produzione, quindi ho esperienza con i debugger per questi linguaggi (Turbo Pascal, Turbo C, GDB / DDD, Visual Studio, Eclipse ).

Un debugger per un linguaggio imperativo normalmente consente di

  • Imposta i punti di interruzione sulle istruzioni ed esegui il programma fino al successivo punto di interruzione.
  • Esegue il programma una dichiarazione alla volta, con l'opzione di immettere una chiamata funzione / metodo o saltare alla seguente istruzione.

Quando l'esecuzione del programma è in pausa, è possibile esaminare lo stato corrente del programma, cioè:

  • Controlla il contenuto della pila per vedere l'annidamento corrente delle chiamate di funzione.
  • Esamina le variabili locali, i parametri delle funzioni effettive, le variabili globali e così via.

Dato che non ho mai lavorato con un linguaggio di programmazione funzionale in un ambiente di produzione, stavo cercando di capire come sarebbe un debugger per un tale linguaggio. Ad esempio:

  • In che modo il debugger percorre il programma se non ci sono affermazioni come in un linguaggio imperativo? Dove si impostano i punti di interruzione?
  • In una lingua con valutazione lenta come Haskell, la sequenza delle chiamate alle funzioni può essere diversa da quella di un linguaggio desideroso: questo farà una grande differenza quando proverai a capire lo stack delle chiamate?
  • E le variabili? Dato che non esiste uno stato, immagino che un debugger mostrerà solo i binding di variabili per l'attuale scope (?), Cioè non ci saranno variabili locali o globali che cambiano valore mentre passo attraverso il programma.

Riassumendo, ci sono caratteristiche generali e comuni dei debugger del linguaggio funzionale che li distinguono chiaramente dai debugger in linguaggio imperativo?

    
posta Giorgio 23.08.2012 - 17:45
fonte

2 risposte

3

In profondità, ogni linguaggio funzionale con radici pesanti nel calcolo lambda è compilato per codice, seguendo da vicino un modello di calcolo astratto. Per haskell è STG-machine, per ocaml / caml-light era CAM-machine. Questo modello di esecuzione di solito ha qualcosa come le variabili locali e una chiara via di esecuzione e il codice sorgente si traduce in codice per la macchina astratta mediante un algoritmo chiaro e ben definito. Quindi, i breakpoint possono essere inseriti nel codice haskell (per esempio) e qualcosa come le variabili locali possono essere determinate, quando viene valutata l'espressione con breakpoint.

Per haskell e altri linguaggi pigri, tuttavia, il problema è che non fa molto bene (eccetto la profilazione). REPL consente di codificare le espressioni con facilità, dal momento che nessun effetto collaterale si allontana dalla definizione del corpo della funzione, quindi non c'è bisogno di eseguire correttamente il debug delle funzioni localmente e l'individuazione localizzerà sicuramente l'errore. La vera fonte di bug di solito è la perdita di spazio dovuta alla pigrizia nel posto sbagliato, e sono necessari strumenti completamente diversi, ispezionando la struttura dell'heap (e alcuni sono disponibili).

    
risposta data 23.08.2012 - 19:42
fonte
3

How does the debugger walk through the program if there are no statements like in an imperative language? Where does one set break points?

La maggior parte dei linguaggi funzionali ha ancora dichiarazioni; lavorano come al solito. Altrimenti, è generalmente in entrata in una chiamata di funzione o occasionalmente in una sottoespressione.

In a language with lazy evaluation like Haskell, the sequence of function calls can be different from that of an eager language: will this make a big difference when trying to understand the call stack?

Non ho fatto molto, ma da quello che ho visto non fa una grande differenza nella pratica. Anche nel peggiore dei casi, si comporta in modo simile a programmi imperativi concorrenti in cui lo stack di chiamate stesso è largamente inutile nel determinare la fonte del bug.

What about variables? Since there is no state, I imagine that a debugger will only show the variable bindings for the current scope (?), i.e. there will be no local or global variables changing value as I step through the program.

Ancora una volta, la maggior parte delle lingue funzionali ha almeno un certo stato. L'impatto maggiore è più simile alla valutazione di un costrutto pigro che causa la stranezza del programma. Questo però non è molto diverso dai costrutti pigri in linguaggi imperativi come i generatori Python o gli enumerabili C #.


Alla fine, non c'è molta differenza nella mia esperienza.

    
risposta data 23.08.2012 - 17:53
fonte