Sunday, May 26, 2013

HASKELL GHCI DEBUGGER INTRODUCTION

HASKELL GHCI DEBUGGER INTRODUCTION

REVISED: Thursday, February 8, 2024




Haskell GHCi debugger.

I. HASKELL GHCI DEBUGGER

Even though lazy evaluation makes it difficult to apply traditional procedural debugging techniques to Haskell, GHCi contains a simple imperative-style debugger which can stop a running computation in order to examine the values of variables. However, breakpoints and single-stepping are only available in interpreted modules; compiled code is invisible to the debugger.

:sprint can be used to see lazy evaluation by telling you if a variable has been evaluated.

GHCi, version 9.8.1: http://www.haskell.org/ghc/  :? for help
Prelude>

Prelude>
let x = 1 + 2 :: Int
Prelude>

Prelude>
:sprint x
x = _  -- Lazy evaluation; therefore, x has not been computed.
Prelude>

Prelude>
x
3 -- A value has been requested for x.
Prelude>

Prelude> :sprint x
x = 3 -- Now Haskell shows a value for x.
Prelude>

A. SAMPLE PROGRAM TO DEBUG

Save the following module as Main.hs to your working directory:

module Main where

import System.IO
import Data.Char(toUpper)

main :: IO ()
main = do 
       inh <- openFile "input.txt" ReadMode
       outh <- openFile "output.txt" WriteMode
       mainloop inh outh
       hClose inh
       hClose outh

mainloop :: Handle -> Handle -> IO ()
mainloop inh outh = 
    do ineof <- hIsEOF inh
       if ineof
           then return ()
           else do inpStr <- hGetLine inh
                        hPutStrLn outh (map toUpper inpStr)
                        mainloop inh outh

Then open GHCi and :load Main.hs as shown below:

Prelude>  :load Main
[1 of 1] Compiling Main             ( Main.hs, interpreted )
Ok, modules loaded: Main.
Prelude>

B. BREAKPOINTS

The GHCi debugger enables dynamic breakpoints and intermediate values observation.

The GHCi debugger provides a way of inspecting code. :b N sets a break point in the loaded module at a specific line. When a breakpoint is set on a particular line, GHCi sets the breakpoint on the leftmost subexpression that begins and ends on that line.

As shown below, breakpoints can be set many different ways. For example, by line; by line and column; by module; by module and line; and by module line and column.

:break line
:break line column
:break module
:break module line
:break module line column

Prelude>  :show breaks
No active breakpoints.
Prelude>

An example of setting a ":break module" breakpoint is shown below:

Prelude>  :break mainloop
Breakpoint 0 activated at Main.hs:(15,1)-(21,35)
Prelude>

As shown above the module mainloop starts on (15,1) line 15 column 1 and ends on (21,36) line 21 column 36.

Prelude>  :show breaks
[0] Main Main.hs:(15,1)-(21,35)
Prelude>

Prelude>  :delete *                      -- To delete all breakpoints at once.
Prelude>

Prelude>  :show breaks
No active breakpoints.
Prelude>    

When stopped at a breakpoint or single-step, GHCi binds the variable _result to the value of the currently active result expression. 

To delete a breakpoint, use the :delete <number> command with the number given in the output from :show breaks.

:list is used to list the source code around the current breakpoint.

:trace  can be used once you have hit a breakpoint, to continue to the next breakpoint, recording the history as you go along.

The following first outputs the message then returns the value of f x.

trace :: String -> a -> a
trace ("calling f with x = " ++ show x) (f x)

The trace function outputs the trace message given as its first argument, before returning the second argument as its result.

:back and :forward allow you to go up and down the list of evaluated expressions and inspect each one.

C. EXAMPLES

As shown below, GHCi uses the flag -fbreak-on-exception to allow you to break into program execution at an arbitrary point.

Prelude>  :set -fbreak-on-exception
Prelude>  

Prelude>  import Debug.Trace
Prelude>

Prelude>  :trace main
Stopped at <exception thrown>
_exception :: e = _
Prelude>

Prelude>  :list
Unable to list source for <exception thrown>
Try :back then :list
Prelude>

Prelude>  :back
Logged breakpoint at Main.hs:19:30-41
_result :: IO String
inh :: Handle
Prelude>

Prelude>  :list
18             then return () 
19             else do inpStr <- hGetLine inh                                  
20                     hPutStrLn outh (map toUpper inpStr) 
Prelude>

Prelude>  :hist
-1  : mainloop (Main.hs:19:30-41)
-2  : mainloop (Main.hs:(17,8)-(21,36))
-3  : mainloop (Main.hs:16:17-26)
-4  : mainloop (Main.hs:(16,5)-(21,36))
-5  : mainloop (Main.hs:(15,1)-(21,36))
-6  : main (Main.hs:10:8-24)
-7  : main (Main.hs:9:16-46)
<end of history>
Prelude>    

II. CONCLUSION

In this tutorial, you have been introduced to the Haskell GHCi debugger.

III. REFERENCES

Bird, R. (2015). Thinking Functionally with Haskell. Cambridge, England: Cambridge University Press.

Davie, A. (1992). Introduction to Functional Programming Systems Using Haskell. Cambridge, England: Cambridge University Press.

Goerzen, J. & O'Sullivan, B. &  Stewart, D. (2008). Real World Haskell. Sebastopol, CA: O'Reilly Media, Inc.

Hutton, G. (2007). Programming in Haskell. New York: Cambridge University Press.

Lipovača, M. (2011). Learn You a Haskell for Great Good!: A Beginner's Guide. San Francisco, CA: No Starch Press, Inc.

Thompson, S. (2011). The Craft of Functional Programming. Edinburgh Gate, Harlow, England: Pearson Education Limited.