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> let x = 1 + 2 :: Int
Prelude>
Prelude> :sprint x
x = _ -- Lazy evaluation; therefore, x has not been computed.
Prelude> :sprint x
x = _ -- Lazy evaluation; therefore, x has not been computed.
Prelude>
Prelude> x
3 -- A value has been requested for x.
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 DEBUGx = 3 -- Now Haskell shows a value for x.
Prelude>
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)An example of setting a ":break module" breakpoint is shown below:
Prelude> :break mainloop
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.
: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> :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>
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.