repos / debug-trace-file.hs.git


debug-trace-file.hs.git / src / Debug / Trace
Evgenii Akentev  ·  2023-06-23

File.hs

  1-----------------------------------------------------------------------------
  2-- |
  3-- Module      :  Debug.Trace.File
  4-- Maintainer  :  i@ak3n.com
  5--
  6-- Like Debug.Trace but writing to files (when eventlog is too much).
  7--
  8-- The functions use 'appendFile' and append to files by default.
  9-- The functions with suffix W (like 'traceFileW', 'traceFileIdW', etc) use 'writeFile'.
 10-----------------------------------------------------------------------------
 11
 12module Debug.Trace.File
 13  (
 14    -- * Tracing to files
 15    traceFile
 16  , traceFileW
 17
 18  , traceFileId
 19  , traceFileIdW
 20
 21  , traceFileShow
 22  , traceFileShowW
 23
 24  , traceFileShowId
 25  , traceFileShowIdW
 26
 27  , traceFileWith
 28  , traceFileWithW
 29
 30  , traceFileShowWith
 31  , traceFileShowWithW
 32
 33  , traceFileM
 34  , traceFileMW
 35
 36  , traceFileShowM
 37  , traceFileShowMW
 38  ) where
 39
 40import Data.Functor (($>))
 41import System.IO.Unsafe (unsafePerformIO)
 42
 43-- $setup
 44-- >>> import Prelude
 45
 46{-|
 47The 'traceFile' function appends to the provided file path given as its first argument,
 48the trace message given as its second argument, before returning the third argument as its result.
 49
 50For example, this returns the value of @f x@ and outputs the message to "\/tmp\/message".
 51
 52>>> let x = 123; f = show
 53>>> traceFile "/tmp/message" ("calling f with x = " ++ show x) (f x)
 54"123"
 55>>> readFile "/tmp/message"
 56"calling f with x = 123\n"
 57
 58The 'traceFile' function should /only/ be used for debugging, or for monitoring
 59execution. The function is not referentially transparent: its type indicates
 60that it is a pure function but it has the side effect of outputting the
 61trace message.
 62-}
 63traceFile :: FilePath -> String -> a -> a
 64traceFile = traceInternal appendFile
 65
 66{-|
 67Like 'traceFile' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
 68-}
 69traceFileW :: FilePath -> String -> a -> a
 70traceFileW = traceInternal writeFile
 71
 72{-|
 73Like 'traceFile' but returns the message instead of a third value.
 74
 75>>> traceFileId "/tmp/message" "hello"
 76"hello"
 77>>> readFile "/tmp/message"
 78"hello\n"
 79-}
 80traceFileId :: FilePath -> String -> String
 81traceFileId fp a = traceFile fp a a
 82
 83{-|
 84Like 'traceFileId' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
 85-}
 86traceFileIdW :: FilePath -> String -> String
 87traceFileIdW fp a = traceFileW fp a a
 88
 89{-|
 90Like 'traceFile', but uses 'show' on the argument to convert it to a 'String'.
 91
 92This makes it convenient for printing the values of interesting variables or
 93expressions inside a function. For example here we print the value of the
 94variables @x@ and @y@:
 95
 96>>> let f x y = traceFileShow "/tmp/message" (x,y) (x + y) in f (1+2) 5
 978
 98>>> readFile "/tmp/message"
 99"(3,5)\n"
100-}
101traceFileShow :: Show a => FilePath -> a -> b -> b
102traceFileShow fp = traceFile fp . show
103
104{-|
105Like 'traceFileShow' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
106-}
107traceFileShowW :: Show a => FilePath -> a -> b -> b
108traceFileShowW fp = traceFileW fp . show
109
110{-|
111Like 'traceFileShow' but returns the shown value instead of a third value.
112
113>>> traceFileShowId "/tmp/message" (1+2+3, "hello" ++ "world")
114(6,"helloworld")
115>>> readFile "/tmp/message"
116"(6,\"helloworld\")\n"
117-}
118traceFileShowId :: Show a => FilePath -> a -> a
119traceFileShowId fp a = traceFile fp (show a) a
120
121{-|
122Like 'traceFileShowId' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
123-}
124traceFileShowIdW :: Show a => FilePath -> a -> a
125traceFileShowIdW fp a = traceFileW fp (show a) a
126
127{-|
128Like 'traceFile', but outputs the result of calling a function on the argument.
129
130>>> traceFileWith "/tmp/message" fst ("hello","world")
131("hello","world")
132>>> readFile "/tmp/message"
133"hello\n"
134-}
135traceFileWith :: FilePath -> (a -> String) -> a -> a
136traceFileWith fp f a = traceFile fp (f a) a
137
138{-|
139Like 'traceFileWith' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
140-}
141traceFileWithW :: FilePath -> (a -> String) -> a -> a
142traceFileWithW fp f a = traceFileW fp (f a) a
143
144{-|
145Like 'traceFileWith', but uses 'show' on the result of the function to convert it to
146a 'String'.
147
148>>> traceFileShowWith "/tmp/message" length [1,2,3]
149[1,2,3]
150>>> readFile "/tmp/message"
151"3\n"
152-}
153traceFileShowWith :: Show b => FilePath -> (a -> b) -> a -> a
154traceFileShowWith fp f = traceFileWith fp (show . f)
155
156{-|
157Like 'traceFileWith' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
158-}
159traceFileShowWithW :: Show b => FilePath -> (a -> b) -> a -> a
160traceFileShowWithW fp f = traceFileWithW fp (show . f)
161
162{-|
163Like 'traceFile' but returning unit in an arbitrary 'Applicative' context. Allows
164for convenient use in do-notation.
165
166>>> :{
167do
168    x <- Just 3
169    traceFileM "/tmp/message" ("x: " ++ show x)
170    y <- pure 12
171    traceFileM "/tmp/message" ("y: " ++ show y)
172    pure (x*2 + y)
173:}
174Just 18
175>>> readFile "/tmp/message"
176"x: 3\ny: 12\n"
177-}
178traceFileM :: Applicative f => FilePath -> String -> f ()
179traceFileM fp string = traceFile fp string $ pure ()
180
181{-|
182Like 'traceFileM' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
183-}
184traceFileMW :: Applicative f => FilePath -> String -> f ()
185traceFileMW fp string = traceFileW fp string $ pure ()
186
187{-|
188Like 'traceFileM', but uses 'show' on the argument to convert it to a 'String'.
189
190>>> :{
191do
192    x <- Just 3
193    traceFileShowM "/tmp/message" x
194    y <- pure 12
195    traceFileShowM "/tmp/message" y
196    pure (x*2 + y)
197:}
198Just 18
199>>> readFile "/tmp/message"
200"3\n12\n"
201-}
202traceFileShowM :: (Show a, Applicative f) => FilePath -> a -> f ()
203traceFileShowM fp = traceFileM fp . show
204
205{-|
206Like 'traceFileShowM' but uses `writeFile` instead of `appendFile` which means will overwrite the contents of the file.
207-}
208traceFileShowMW :: (Show a, Applicative f) => FilePath -> a -> f ()
209traceFileShowMW fp = traceFileMW fp . show
210
211traceInternal :: (FilePath -> String -> IO ()) -> FilePath -> String -> a -> a
212traceInternal writeFunc fp str val = unsafePerformIO $! writeFunc fp (str ++ "\n") $> val