Domanda

proveniente dalla comunità di OCaml, sto cercando di imparare un po 'di Haskell. La transizione va abbastanza bene, ma io sono un po 'confuso con il debug. Ho usato per mettere (un sacco di) "printf" nel mio codice OCaml, per ispezionare alcuni valori intermedi, o come bandiera per vedere dove il calcolo esatto non è riuscita.

Dato che printf è un IO azione, devo alzare tutto il codice mia Haskell all'interno del IO Monade di essere in grado a questo tipo di debug? O c'è un modo migliore per fare questo (io davvero non voglio farlo a mano se può essere evitato)

Trovo anche il traccia Funzione: http://www.haskell.org/haskellwiki/Debugging#Printf_and_friends che sembra esattamente quello che voglio, ma non capisco il suo tipo: non c'è IO da nessuna parte! Qualcuno mi può spiegare il comportamento della funzione di traccia?

È stato utile?

Soluzione

trace è il più facile da utilizzare il metodo per il debug. Non è nella IO esattamente per la ragione lei ha: non c'è bisogno di alzare il codice in monade IO. E 'implementato come questo

trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
    putTraceMsg string
    return expr

Quindi non c'è IO dietro le quinte, ma unsafePerformIO è usato per scappare fuori di esso. Questa è una funzione che rompe potenzialmente trasparenza referenziale che si può intuire guardando il suo tipo IO a -> a ed anche il suo nome.

Altri suggerimenti

trace è semplicemente fatto impura. Il punto di monade IO è quello di preservare la purezza (senza IO inosservato sistema tipo) e definire l'ordine di esecuzione delle istruzioni, che altrimenti sarebbero praticamente undefined attraverso valutazione pigra.

Il rischio tuttavia, si può comunque incidere insieme alcuni IO a -> a, vale a dire eseguire impura IO. Si tratta di un hack e, naturalmente, "soffre" di valutazione pigra, ma è quello che traccia semplicemente lo fa per il gusto di debug.

Tuttavia, però, si dovrebbe probabilmente andare altri modi per il debug:

  1. La riduzione della necessità di debug valori intermedi

    • Scrivi piccole, chiare, funzioni, generici riutilizzabili cui correttezza è evidente.
    • Unire i pezzi giusti per maggiori pezzi corretti.
    • test o provare pezzi in modo interattivo.
  2. Usa i punti di interruzione, ecc (il debug del compilatore-based)

  3. Utilizzare monadi generici. Se il codice è monadica comunque, scriverlo indipendente da una monade di cemento. Uso type M a = ... invece di IO ... pianura. Si può poi facilmente combinare monadi mediante trasformatori e mettere una monade debug su di esso. Anche se la necessità di monadi è andato, si può solo inserto Identity a per i valori puri.

Per quel che vale, ci sono in realtà due tipi di "debug" in questione qui:

  • Logging valori intermedi, come il valore di un particolare sottoespressione ha su ciascuna chiamata in una funzione ricorsiva
  • Controllo il comportamento di esecuzione della valutazione di un'espressione

In un linguaggio imperativo rigoroso questi di solito coincidono. In Haskell, che spesso non lo fanno:

  • Registrazione valori intermedi può cambiare il comportamento in esecuzione, come ad esempio forzando la valutazione dei termini che altrimenti sarebbero scartati.
  • Il processo effettivo di calcolo può differire notevolmente dalla struttura apparente di un'espressione per pigrizia e sottoespressioni condivise.

Se si desidera solo per tenere un registro dei valori intermedi, ci sono molti modi per farlo - per esempio, invece di sollevare tutto in IO, una semplice monade Writer sarà sufficiente, essendo questo equivale a disporre delle funzioni di restituire un 2 -tuple dal risultato reale e un valore dell'accumulatore (una sorta di lista, tipicamente).

E 'anche solito non necessario mettere tutto nella monade, solo le funzioni che hanno bisogno di scrivere il valore "log" - per esempio, si può scomporre solo le sottoespressioni che potrebbe aver bisogno fare registrazione, lasciando la logica principale pura, e rimontare il calcolo complessiva combinando le funzioni pure ei calcoli di registrazione nel modo consueto con fmaps e quant'altro. Tenete a mente che Writer è una specie di brutta copia di una monade: con alcun modo di leggere da il registro, ma solo scrivere ad esso, ogni calcolo è logicamente indipendente dal suo contesto, che rende più facile per cose Juggle intorno.

Ma in alcuni casi, anche di quella Overkill -. Per molte funzioni pure, appena in movimento sottoespressioni al toplevel e provare cose fuori nel REPL funziona abbastanza bene

Se si vuole ispezionare in realtà il comportamento in fase di esecuzione di codice puro, tuttavia - per esempio, di capire perché un diverge sottoespressione - non v'è, in generale, alcun modo per farlo da altro codice puro - in realtà, questo è essenzialmente il definizione di purezza. Quindi, in questo caso, non hai scelta, ma per gli strumenti di uso che esistono "al di fuori" della lingua pura: o funzioni impuri come i unsafePerformPrintfDebugging - errr, voglio dire trace - o un ambiente di runtime modificato, come ad esempio il debugger GHCi <. / p>

trace tende anche a un eccesso di valutare il suo argomento per la stampa, perdendo un sacco di benefici della pigrizia nel processo.

Se si può aspettare fino a quando il programma è finito prima di studiare l'uscita, poi accatastamento un Writer monade è l'approccio classico alla attuazione di un logger. Io uso questo qui per restituire un risultato set dal codice impura HDBC.

Bene, dal momento che tutta la Haskell è costruito attorno principio della valutazione pigra (in modo che l'ordine dei calcoli è, infatti, non deterministico), l'uso di di printf rendono molto poco senso.

Se REPL + ispezionare i valori risultanti non è davvero abbastanza per il vostro debug, involucro tutto in IO è l'unica scelta (ma non è il modo giusto di programmazione Haskell).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top