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