Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/knz/hs-tracer

Tracing utilities for Haskell programmers.
https://github.com/knz/hs-tracer

Last synced: 26 days ago
JSON representation

Tracing utilities for Haskell programmers.

Awesome Lists containing this project

README

        

==============
Debug.Tracer
==============

Tracing utilities for Haskell code
==================================

This library contains **Debug.Tracer**, a module that provides some
support for "print debugging" of Haskell code, and *even for pure code*: no
explicit I/O typing is required.

Note: the file defining this documentation (``README.lhs``) is itself
a Haskell program, which can be run for testing the library.

.. contents::

How to use
----------

The following code defines a function ``myfact`` that computes
the factorial of an integer number::

> import Debug.Tracer
>
> -- This type declaration aliases 'PureTracer PosStack',
> -- to make its name shorter for reuse.
> type Tr a = PureTracer PosStack a
>
> -- An example function using our tracer
> myfact :: Int -> Tr Int
> myfact n = do
> trace $ "entering with n = " ++ (show n)
> if n == 1 then return n
> else do
> r <- enter $ myfact (n - 1)
> trace $ "just computed r = " ++ (show r)
> return (n * r)
>
> -- The function can be used in a pure context, eg:
> myfact' :: Int -> Int
> myfact' = (runTracer "myfact") . myfact

As the example demonstrates, the **Tracer** modules allows a
programmer to write pure code in a semi-imperative style, with the
``trace`` and ``enter`` actions helping to report execution progress.

For example, if we extend the example to become
a fully-fledged program::

>
> main :: IO ()
> main = do
> putStrLn $ show $ myfact' 10

Then this program would print a trace like the following::

1 myfact +1: entering with n = 10
4 myfact +2> +1: entering with n = 9
7 myfact +2> +2> +1: entering with n = 8
10 myfact +2> +2> +2> +1: entering with n = 7
13 myfact +2> +2> +2> +2> +1: entering with n = 6
16 myfact +2> +2> +2> +2> +2> +1: entering with n = 5
19 myfact +2> +2> +2> +2> +2> +2> +1: entering with n = 4
22 myfact +2> +2> +2> +2> +2> +2> +2> +1: entering with n = 3
25 myfact +2> +2> +2> +2> +2> +2> +2> +2> +1: entering with n = 2
28 myfact +2> +2> +2> +2> +2> +2> +2> +2> +2> +1: entering with n = 1
32 myfact +2> +2> +2> +2> +2> +2> +2> +2> +2> +3: just computed r = 1
36 myfact +2> +2> +2> +2> +2> +2> +2> +2> +3: just computed r = 2
40 myfact +2> +2> +2> +2> +2> +2> +2> +3: just computed r = 6
44 myfact +2> +2> +2> +2> +2> +2> +3: just computed r = 24
48 myfact +2> +2> +2> +2> +2> +3: just computed r = 120
52 myfact +2> +2> +2> +2> +3: just computed r = 720
56 myfact +2> +2> +2> +3: just computed r = 5040
60 myfact +2> +2> +3: just computed r = 40320
64 myfact +2> +3: just computed r = 362880
3628800

Overview
--------

The library can be used at two levels. At the "user" level,
the functions ``trace``, ``enter`` and ``label`` are defined
and are ready to use in do-blocks, together with
a suitable type signature as in the example above.

Three monads are pre-defined at this level:

- ``PureTracer`` traces pure computations.
- ``MaybeTracer`` extends the ``Maybe`` monad with tracing.
- ``IOTracer`` extends the ``IO`` monad with tracing.

In addition, **Debug.Trace** also provides a `monad
transformer`__ called **TraceT**, which can instrument any other monad
with tracing.

.. __: http://book.realworldhaskell.org/read/monad-transformers.html

For example, to equip the ``State`` monad with tracing::

type Tr a = TraceT PosStack State a

The icing on the cake is that **TraceT** not only instruments
`Monad`__ types, but also any `Applicative`__ and `Functor`__ types.

.. __: http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Applicative.html
.. __: http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Monad.html#t:Monad
.. __: http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Monad.html#t:Functor

The type "``TraceT PosXX f``" is a ``Functor`` if ``f`` is a functor,
is ``Applicative`` if ``f`` is applicative, and is a ``Monad`` if ``f`` is a monad.

More examples
-------------

Stacking vs. non-stacking with ``enter``
````````````````````````````````````````

If a (pure) function "looks" like a flat loop, chances are its trace
will be flat, too::

> let
> myfact2 :: Int -> Int -> Tr Int
> myfact2 r n = do
> trace $ "processing n = " ++ (show n) ++ ", r = " ++ (show r)
> if n == 1 then return r
> else myfact2 (n * r) (n - 1)
> putStrLn $ show $ runTracer "myFact2" $ myfact2 1 10

This program, which avoids using ``enter``, would print::

1 myFact2 +1: processing n = 10, r = 1
2 myFact2 +2: processing n = 9, r = 10
3 myFact2 +3: processing n = 8, r = 90
4 myFact2 +4: processing n = 7, r = 720
5 myFact2 +5: processing n = 6, r = 5040
6 myFact2 +6: processing n = 5, r = 30240
7 myFact2 +7: processing n = 4, r = 151200
8 myFact2 +8: processing n = 3, r = 604800
9 myFact2 +9: processing n = 2, r = 1814400
10 myFact2 +10: processing n = 1, r = 3628800
3628800

The "stacking" in the output follows the uses of the action
``enter``. To avoid stacking, simply leave ``enter`` away.

Relative counts with ``label``
``````````````````````````````

In a complex computation, the action ``label`` can mark basic blocks,
as follows::

> let
> complex :: Int -> Tr Int
> complex n = do
> label "start"
> x <- return $ 1 - n
> y <- return $ 2 * n
> t <- return $ x * y
> trace $ "here t = " ++ (show t)
> u <- return $ y - t
>
> label "middle"
> v <- return $ t * x
> trace $ "v = " ++ (show v)
>
> m <- return $ n * n
> o <- return $ m + m + u + v
> label "end"
> trace $ "returning o = " ++ (show o)
> return o
> putStrLn $ show $ runTracer "complex" $ complex 10

This could print the following trace::

12 complex start+11: here t = -180
21 complex middle+5: v = 1620
30 complex end+2: returning o = 2020
2020

Note how the count on the left is global (shared by the entire tracing
compound), whereas the count on the right is local, relative to the
point a label was last positioned.

Ordering with other side-effects
``````````````````````````````````

Traces play well with other side effects. For example,
I/O actions are properly ordered::

> let
> someio :: Int -> TrIO ()
> someio n = do
> d <- return $ n + n
> trace "before io"
> lift $ putStrLn $ "d = " ++ (show d)
> trace "after io"
> return ()
>
> runTracerT "someio" (someio 10)

This program would print as expected::

4 someio +4: before io
d = 20
8 someio +8: after io

With the message "``d = 20``" properly interleaved with trace
messages.

Note: the examples above also use the following type declaration::

> type TrIO a = IOTracer PosStack a