------------------------------------------------------------------------ -- The Agda standard library -- -- An example showing how the Debug.Trace module can be used ------------------------------------------------------------------------ {-# OPTIONS --cubical-compatible --rewriting --guardedness #-} module README.Debug.Trace where ------------------------------------------------------------------------ -- Sometimes compiled code can contain bugs. -- Whether caused by the compiler or present in the source code already, they -- can be hard to track. A primitive debugging technique is to strategically -- insert calls to tracing functions which will display their String argument -- upon evaluation. open import Data.String.Base using (_++_) open import Debug.Trace -- We can for instance add tracing messages to make sure an invariant is -- respected or check in which order evaluation takes place in the backend -- (which can inform our decision to use, or not, strictness primitives). -- In the following example, we define a division operation on natural numbers -- using the original dividend as the termination measure. We: -- 1. check in the base case that when the fuel runs out then the updated dividend -- is already zero. -- 2. wrap the calls to _∸_ and go in respective calls to trace to see when all -- of these thunks are forced: are we building a big thunk in go's second -- argument or evaluating it as we go? open import Data.Maybe.Base open import Data.Nat.Base open import Data.Nat.Show using (show) div : ℕ → ℕ → Maybe ℕ div m zero = nothing div m n = just (go m m) where -- invariants: m ≤ fuel -- result : m / n go : (fuel : ℕ) (m : ℕ) → ℕ go zero m = trace ("Invariant: " ++ show m ++ " should be zero.") zero go (suc fuel) m = let m′ = trace ("Thunk for step " ++ show fuel ++ " forced") (m ∸ n) in trace ("Recursive call for step " ++ show fuel) (suc (go fuel m′)) -- To observe the behaviour of this code, we need to compile it and run it. -- To run it, we need a main function. We define a very basic one: run div, -- and display its result if the run was successful. -- We add two calls to trace to see when div is evaluated and when the returned -- number is forced (by a call to show). open import Level using (0ℓ) open import IO main : Main main = let r = trace "Call to div" (div 4 2) j = λ n → trace "Forcing the result wrapped in just." (putStrLn (show n)) in run (maybe′ j (pure _) r) -- We get the following trace where we can see that checking that the -- maybe-solution is just-headed does not force the natural number. Once forced, -- we observe that we indeed build a big thunk on go's second argument (all the -- recursive calls happen first and then we force the thunks one by one). -- Call to div -- Forcing the result wrapped in just. -- Recursive call for step 3 -- Recursive call for step 2 -- Recursive call for step 1 -- Recursive call for step 0 -- Thunk for step 0 forced -- Thunk for step 1 forced -- Thunk for step 2 forced -- Thunk for step 3 forced -- Invariant: 0 should be zero. -- 4 -- We also notice that the result is incorrect: 4/2 is 2 and not 4. We quickly -- notice that (div m (suc n)) will perform m recursive calls no matter what. -- And at each call it will put add 1. We can fix this bug by adding a new first -- equation to go: -- go fuel zero = zero -- Running the example again we observe that because we now need to check -- whether go's second argument is zero, the function is more strict: we see -- that recursive calls and thunk forcings are interleaved. -- Call to div -- Forcing the result wrapped in just. -- Recursive call for step 3 -- Thunk for step 3 forced -- Recursive call for step 2 -- Thunk for step 2 forced -- 2