1 / 30

Debugging Haskell by Observing Intermediate Data Structures

Debugging Haskell by Observing Intermediate Data Structures. Andy Gill Galois Connections andy@galconn.com www.cse.ogi.edu/~andy. Debugging any program…. Debugging is locating a misunderstanding Finding the difference between What you told the computer to do

shubha
Download Presentation

Debugging Haskell by Observing Intermediate Data Structures

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Debugging Haskell by Observing Intermediate Data Structures Andy Gill Galois Connections andy@galconn.com www.cse.ogi.edu/~andy

  2. Debugging any program… • Debugging is locating a misunderstanding • Finding the difference between • What you told the computer to do • What you think you told the computer to do • Involves running code • Sometimes about finding out what someone else told the computer to do • A Debugger must never, never lie • You are gathering evidence, the debugger is under oath

  3. Traditional debugging tools • The “printf” debuggers • Insert printf statements into code • Generally the dump and inspect approach • Written Statements • CLI or GUI Interactive Debuggers • GDB • Visual Studio • Witnesses

  4. Tracer Debuggers • Trace reduction steps, producing extra output • Present dynamically the internal reduction graph • Two ways of getting the trace – both compiler specific • Instrumenting the code via transformations • Use a modified internal reduction interpreter • … There is another way… • Two major technical problems • The size of the traces can be huge • The structures that the user might browse are also huge

  5. Algorithmic Debugging • Also called declarative debugging • Semi-automated binary search • The system asks Yes/No questions, starting at “main” • If a function is giving incorrect results • One of the sub functions must be faulty • Or this function must be faulty • Can use oracles to good effect • Expensive asserts • Strange interactions with IO

  6. Work on Haskell Debuggers • Tracing • ART/HAT project – York (nhc) • HOOD – OGI • GHood, HOOD in NHC, HOOD in Hugs. • Declarative • Freja – Hendrik Nilsson • Budda – Melbourne, Australia • Testing Frameworks • Quickcheck – Chalmers • Auburn – York • Look at www.haskell.org/debugging

  7. 3 : 4 : 0 : 8 : [] 8 : 0 : 4 : 3 : [] 3408 : 340 : 34 : 3 : [] 3408 : 340 : 34 : 3 : 0 : … 3408 Understanding Haskell execution natural :: Int -> [Int] natural = reverse  . map (`mod` 10) . takeWhile (/= 0)  . iterate (`div` 10) Main> natural 3408 [3,4,0,8] :: [Int]

  8. The Haskell Object Observation Debugger • Provide combinators for debugging • STG Hugs or GHC or NHC (others will follow) • Frustrated Haskell programmer uses combinators to annotate their code, and re-runs the program • The execution of the Haskell program allows observations to be made internally • At the termination of the Haskell execution, observed structures are displayed to stderr in a pretty printed form • Think written statements, but not witnesses…

  9. Using trace for debugging • Function called trace trace :: String -> a -> a • Print the string to stderr • & return the second argument • All Haskell systems have this function

  10. trace can help… foo (x:xs) (y:xs) = … foo [] [] = … Can rewrite this as foo x y | trace (“foo:” ++ show (x,y)) False = undefined foo (x:xs) (y:xs) = … foo [] [] = …

  11. Problems with trace • Invasive to your code foo (n + 1) => let r = foo (n + 1) in trace (show(n+1,r)) r • Evaluation of first argument can cause other traces to fire, mid trace. • End up with a spaghetti output • Perhaps trace should be evaluate its argument? • Can change the evaluation order of your program! • Very Bad News • The best we had – until now…

  12. Our debugging combinator Provide a way of annotating a specific expression with a label. observe :: (Observable a) => String -> a -> a boring = observe “after” . reverse . observe “during” . reverse . observe “before” Similar to probe in Hawk, but works over most data types.

  13. First example Main> printO (take 2 (observe “list” [1..10])) printO :: Show a => a -> IO () • Opens the debugging context • evaluates the argument • with possible observations made • Closes the debugging context, pretty prints observed structures. ( 1 : 2 : _)

  14. Second example printO ( let lst = [1..10] lst1 = take 2 (observe “list” lst) in sum (lst ++ lst1) ) ( 1 : 2 : _)

  15. Annotating natural Add observe annotations to natural, capturing the flow between the subcomponents. natural = reverse . observe "after map …” . map (`mod` 10) . observe "after takeWhile …” . takeWhile (/= 0) . observe "after iterate …” . iterate (`div` 10) Focuses on the intermediate data structures

  16. Output from debugging natural -- after iterate (`div` 10) { (3408 : 340 : 34 : 3 : 0 : _) } -- after takeWhile (/= 0) { ( 3408 : 340 : 34 : 3 : [] ) } -- after map (`mod` 10) { ( 8 : 0 : 4 : 3 : [] ) }

  17. What can we observe? • Base types • Int, Char, Integer, Bool, Float, Double, () • Structured Types • Tuples, Lists, Maybe, Arrays • Exceptions? • What about Monads? (List and Maybe are Monads). • What about Functions? observe :: (Observe a,Observe b) => String -> (a -> b) -> a -> b • What does this definition mean?

  18. Observing functions • A observed function • Only called a specific arguments • Finite map from argument to result • For observational purposes, functions are • A set of pairs, representing argument and result • Isomorphic to {(a,b)} • Is this a Set or a Bag? • We can pretty print in a Haskell like manner

  19. Example of function observation Main> printO (map (observe "null" null) [[],[1..]]) -- null { \ [] -> True , \ (_: _) -> False } Shows only the specific invocations used.

  20. Example of higher order function observation Main> printO $ (observe “map null” map null [[],[1..]]) -- map null { \ { \ [] -> True , \ (_ : _) -> False } ([] : (_ : _) : []) -> (True : False : []) }

  21. Debugging natural (again) Now use HO debugging natural = . observe “reverse” reverse . observe "map (`mod` 10)”map (`mod` 10) . observe "takeWhile (/= 0)”takeWhile (/= 0) . observe "iterate (`div 10)”iterate (`div` 10) Focus is now what the sub-components do

  22. Output from debugging natural -- iterate (`div' 10)  \ { \ 3408 -> 340    , \ 340 -> 34     , \ 34 -> 3     , \ 3 -> 0     }     3408 -> (3408 : 340 : 34 : 3 : 0 : _) -- takeWhile (/= 0) \ { \ 3408 -> True   , \ 340 -> True    , \ 34 -> True     , \ 3 -> True     , \ 0 -> False     }      (3408 : 340 : 34 : 3 : 0 : _) -> ( 3408 : 340 : 34 : 3 : [] )

  23. Bad implementation of observe observe :: (Show a) => String -> a -> a observe label a = trace (label ++ “:” ++ show a) a • We’ve changed the strictness of the second argument • Would fail on our naturalexample, because one of the intermediate lists is infinite • Needs to be a member of the class Show • Functions are not Show-able.

  24. p => (:) p,2 => (:) p (:) p Thunk p p,1 (:) Thunk p,2 p,2 Thunk (:) p p,1 (:) p,2 Thunk Thunk p,2,2 Thunk (:) p,2,1 p,1 [] Thunk p,2,1 p,2,2 p,2,2 => [] How does lazy evaluation work? Thunk

  25. Structural observers class Observer a where observer :: a -> (PATH,LABEL) -> a -- Example for (a,b) instance (Observer a,Observe b) => Observe (a,b) where -- observer :: (a,b) -> (PATH,LABEL) -> (a,b) observer (a,b) (path,label) = unsafePerformIO do { sendPacket “… (,) … path … label … ” ; return (observer a (1 : path,label), observer b (2 : path,label)) }

  26. Systematic side effects • fst (observe “…” (f x,44)) • = fst (observer (f x,44) cxt) • = fst (unsafe $ do { “tell about tuple” ; return (observe (f x) (1:cxt),observe 44 (2:cxt)) }) • -- I’m a 2-tuple at path “cxt” ( _ , _ ) • = fst (observe (f x) (1:cxt),observe 44 (2:cxt)) • = observe (f x) (1:cxt) • = seq (f x) $ (unsafe $ do { “tell about the number” ; return (f x) }) • = unsafe $ do { “tell about the number” ; return 99 } • -- I’m an 99 at path “1 : cxt” ( 99 , _ ) • = 99

  27. Status and future of HOOD • Current Status • V0.1 - Available on web: www.haskell.org/hood • Works with Hugs, GHC, STG Hugs, NHC • Handles GHC threads fine • Can catch and observe exceptions (errors, ^C, etc). -- list ( 1 : 2 : error “boom” : _ ) • The Future • V0.2 – Interactive browser via XML file (already in NHC’s version) • Polymorphic observations (using RTS hooks) • Operational semantics for the debugger? • Extensions • GHOOD – Shows trees instead of pretty printed Haskell. • Quickcheck – for showing counterexamples.

  28. Conclusions • Haskell has something resembling a debugging tool that works on real Haskell • Type classes can be used to augment a data structure evaluation with side-effecting functions • Observation of non-trivial examples is possible

  29. Demo … import Observe n = 10 x1 = foldr (+) 0 [1..n] y1 = foldr (observe "Add" (+)) 0 (observe "input" [1..n]) z1 = printO y1 x2 = foldl (+) 0 [1..n] x3 = foldr (+) 0 (reverse [1..n]) y3 = foldr (+) 0 (take 4 (observe "revlist" (reverse (observe "input" [1..n])))) z3 = printO x3 x4 = foldl (+) 0 (reverse [1..n]) y4 = foldl (+) 0 (observe "revlist" (reverse (observe "input" [1..n]))) z4 = printO y4

  30. Homework • Download HOOD • Download HOOD documentation  • Investigate the following • foldr (+) 0 [1..n] • foldl (+) 0 [1..n] • foldr (+) 0 (reverse [1..n]) • foldl (+) 0 (reverse [1..n])

More Related