Diagnostic output in Haskell

I learned programming in imperative languages, mainly C ++ and C, so the functional approach is very new to me.

When I wrote functions / methods before, I usually took an "incremental" approach (as most people probably do): writing a small piece of code and then checking if the results are still as expected (usually just printing them to stdout using printf or std :: cout), refining the algorithm, improving the algorithm, and then checking how expected the results are (usually just printing them to stdout with printf or std :: cout), refining ... I very rarely wrote whole methods in one piece.

Essential to this "incremental" approach is the ability to have diagnostic output (printf or std :: cout in my example above). But in Haskell (as far as I understand it at the moment) I would have to change the signature of my function if I wanted to - say - use 'putStrLn' to write something to stdout, because 'putStrLn' only returns IO Monad which contains information which I want to print, but doesn't print it at the time 'putStrLn' is called, right? So every time I want to use "putStrLn" for diagnostic output I have to change the signature of the current function and the way all my other functions call it, etc.

So, is there a cheap and easy way to print the value of a function's "local variable" to standard output?

Or is it just the fact that I am asking, that this is a sign that I do not understand a fundamental part of programming in Haskell?

+3


source to share


6 answers


It's weird because I'm without a Read Eval Print Loop (REPL) in languages ​​you're used to, I'm forever disappointed at how much my code needs to be checked as I go. The REPL is fundamental to incremental code development; you can use it to test your code without having to add a bunch of print statements. In the meantime, there is no need to know about it. ”

  • Open GHCi at the same time as your editor.
  • Write smaller, single-purpose functions. It looks like bizzare at first, but a functional application is the basic unit of work in Haskell and doesn't have the kind of overhead that you get in imperative languages.
  • Every time you write a function, do it :r

    in GHCi and test it with a variety of inputs.
  • Haskell is very dense, so what's worth doing in a separate function is much shorter on screen than you're used to.

Sometimes you end up with a long monadic computation or something. GHCi allows you to set breakpoints - use them to add instructions to the print statement, because you can bypass and explore a little more without editing your code, and most importantly, you don't need to add Show constraints to your type signatures.



When you're done, you can manually inline the gratuitously short helper functions and compile with ghc -O2

.

(Using manually added print statements or module Debug.Trace

is a complete pain compared to this in my experience.)

Summary . Whenever possible, avoid editing code while testing. Use GHCi a lot.

+8


source


There is no good way to do what you want. You can get close to Debug.Trace

, but I don't recommend it while learning because of Haskell's neoclassical evaluation order. Haskell doesn't work by consistently setting the value of "variables" in the way languages ​​like C and C ++ do. Because it's lazy, Haskell expressions are evaluated in a usage-dependent order, so incremental value doesn't work.

Haskell is an expression oriented language. Use this to your advantage:



  • Write short functions. It's easier to understand what each function does. Most functions should be one line per equation, and real "one liners" should be shared.
  • Use REPL. You must constantly experiment with your code in GHCi
  • Use a type system. Haskell's type system is orders of magnitude more useful than the type systems in the most imperative languages. Indicates the intent of the document on the machine. You cannot count on understanding code without understanding types. When writing code, once you get the types right, you do it the most.

Combine the above suggestions. You can get the type of an expression in GHCi with :t

.

+13


source


You can quickly add unclean debug output to a pure function using trace

the Debug.Trace module function . It is a function that returns its second argument with the added side effect of printing the first argument when coercing a second argument / return value.

I think it's perfectly acceptable to temporarily use this for debugging as long as it doesn't end up with any final commits or other permanent code. Also, the order in which messages are printed follows the evaluation order, which is also useful for debugging, but not always the preferred order of output.

If you need to use this very often, it can also be a sign that you need to expose your code to smaller functions, making it easier to test their behavior by simply specifying the arguments and looking at the return value.

+1


source


First, you can debug your functions by loading them into ghci

and playing with them there.

Then you can use trace

from Debug.Trace

to print the string when evaluating the expression. However, keep in mind that because Haskell uses lazy evaluation, in most cases the expression will evaluate at a different time than you expected. See also Wikibooks and Haskell wiki . (Internally trace

uses unsafe calls that allow the output to be printed even within pure code. Usually you shouldn't use them, but that's okay in this particular case.)

+1


source


There is a fairly short example here on how to build something similar to what you are describing. If I read it correctly, the author creates a simple monad that allows you to type in the middle of a computation, so to speak.

0


source


Approximation:

  • Debugging GHCi is a way to print local variables without cluttering your code.

  • The modified transformer WriterT, strict or lazy, can serialize your logs if you return your tracked value, combined with the result.

{-# LANGUAGE PackageImports #-}
-- import qualified "transformers" Control.Monad.Trans.Writer.Strict as W
import qualified "transformers" Control.Monad.Trans.Writer.Lazy as W

compute:: Int -> Int -> (Int, Int)
compute x y = (result, local)
  where
    local = 2 * x
    result = local + y

test :: (Monad m) => W.WriterT String m Int
test = do
  let (r1, local1) = compute 5 3
  W.tell $ "local1= " ++ show local1 ++ "\n"

  let (r2, local2) = compute 2 2
  W.tell $ "local2= " ++ show local2 ++ "\n"

  return $ r1 + r2

main = do
  (r, logs) <- W.runWriterT test
  putStrLn logs
  putStrLn $ "result= " ++ show r

      

outputs:

local1 = 10
local2 = 4

result = ...
0


source







All Articles