
One of the first things I did when starting a larger haskell project was to write a preprocessor that would replace certain tokens with (token_srcpos (filename, func_name, lineno)) and then write x and x_srcpos versions of the various 'throw' and logging functions. This is extremely handy to have, and since logs are part of my app's UI, it's part of the app itself. I found out later that jhc has a pragma that basically does the same thing, only built in. My preprocessor works but has some problems. Namely, replacing non-qualified tokens requires parsing the source because you want to replace only function calls, not declarations, import / export lists, etc. I get around this because I always import qualified, but still I need to do some hacking to get qualified tokens ('lex' mysteriously doesn't understand them... aren't they in haskell 2010 now?) and not screw up on odd corners like backslash string continuations. Then I start wanting to e.g. replace 'throw' in the same module it's defined in, which really lets the dragons out because now I'm trying to replace unqualified names. Another problem is that the names to replace are hardcoded in the preprocessor. Not a problem for me, but would be for a general tool. My preprocessor works well, but occasionally I do have to go in and fix yet another odd corner that came up. Initially I thought I would only simplistically replace tokens and avoid using a full syntax parser, but to really do it correctly a full parser is needed. And of course this is specific to my style (qualified imports) and project. To do this in full generality really requires the compiler's help. But as I use it now, it's been extremely useful, especially for exceptions which may not carry a unique or easily greppable msg. Or any msg at all. Are there any more sophisticated tools out there that do this? Interest in extending ghc to support SRCLOC_ANNOTATE? Reasons why I don't actually want this after all?

On 19 January 2011 22:43, Evan Laforge
Reasons why I don't actually want this after all?
Are you aware of the magic assert function? http://hackage.haskell.org/packages/archive/base/latest/doc/html/Control-Exc... The compiler spots it and replaces it with a function that raises an exception that contains the source location if the assert fails. It is deeply evil, but you can write a library function like this (untested): getSourceLoc :: (Bool -> a -> a) -> String getSourceLoc my_assert = unsafePerformIO (evaluate (my_assert False (error "Impossible")) `catch` (\(AssertionFailed s) -> return s)) Now your consumers can write: getSourceLoc assert :: String And you will have a value that describes where you are. You can then use this to implement logging, throw errors etc. The annoying part is that you have to explicitly pass in assert from the call site to make sure that you get the right source location reported. Cheers, Max

Now your consumers can write:
getSourceLoc assert :: String
And you will have a value that describes where you are. You can then use this to implement logging, throw errors etc. The annoying part is that you have to explicitly pass in assert from the call site to make sure that you get the right source location reported.
Yes, in fact the 'loch' package does this. Initially I thought that having a dummy argument on every single log call and throw (quite a few of those) would be too much hassle, but if I have to keep fixing the preprocessor I might start reconsidering. Another thing is that performance in logging is pretty critical. When I profile, logging functions wind up in the expensive list if I'm not careful. I don't know bad an unsafePerformIO plus a catch for every log msg is going to be, but it's not going to be as fast as doing the work at compile time.

On 19 January 2011 23:59, Evan Laforge
Another thing is that performance in logging is pretty critical. When I profile, logging functions wind up in the expensive list if I'm not careful. I don't know bad an unsafePerformIO plus a catch for every log msg is going to be, but it's not going to be as fast as doing the work at compile time.
It is possible that GHC's optimiser will let-float the "getSourceLoc assert" application to the top level, which would ensure that you only take the hit the first time. However, I'm not sure about this - do an experiment! Since logging is in IO anyway, perhaps GHC could provide a new magic primitive, (location :: IO String), which was replaced by the compiler with an expression like (return "MyLocation:42:1"). Your consumer could then look like myLogFunction location "extra-info" (myLogFunction is in IO and would deal with >>=ing in the location). This would be much less of a potential performance drag than going through exceptions. Shouldn't be too hard to patch GHC to do this, either. Cheers, Max

On Thu, Jan 20, 2011 at 12:47 AM, Max Bolingbroke
On 19 January 2011 23:59, Evan Laforge
wrote: Another thing is that performance in logging is pretty critical. When I profile, logging functions wind up in the expensive list if I'm not careful. I don't know bad an unsafePerformIO plus a catch for every log msg is going to be, but it's not going to be as fast as doing the work at compile time.
It is possible that GHC's optimiser will let-float the "getSourceLoc assert" application to the top level, which would ensure that you only take the hit the first time. However, I'm not sure about this - do an experiment!
I don't read core very well, but with a simple example it appears it does, and I wind up with the unsafePerformIO stuff in a top level CAF of type String. However, when I switch to a more realistic example with an intermediary function, as in: warn assert msg = tell (getSourceLoc assert ++ msg) it looks like each call to 'warn' calls getSourceLoc as a function, so it should take the performance hit each time. So it might be possible to get this to work by embedding all logging logic into the second argument of 'catch' and being careful to check the core.
Since logging is in IO anyway, perhaps GHC could provide a new magic primitive, (location :: IO String), which was replaced by the compiler with an expression like (return "MyLocation:42:1"). Your consumer could then look like
Not necessarily! None of my logging or exceptions are in IO, so it wouldn't work for me. I still like the pragma...

On 20 January 2011 10:48, Evan Laforge
I still like the pragma...
Maybe Template Haskell can help: module Location where import Language.Haskell.TH import Data.Functor ((<$>)) loc :: Q Exp loc = LitE . StringL . show . loc_start <$> location {-# LANGUAGE TemplateHaskell #-} module Main where import Location main = do putStrLn $loc putStrLn $loc putStrLn $loc *Main> main (7,12) (8,12) (9,12) Regards, Bas

On 20 January 2011 14:33, Bas van Dijk
On 20 January 2011 10:48, Evan Laforge
wrote: I still like the pragma...
Maybe Template Haskell can help:
module Location where
import Language.Haskell.TH import Data.Functor ((<$>))
loc :: Q Exp loc = LitE . StringL . show . loc_start <$> location
{-# LANGUAGE TemplateHaskell #-}
module Main where
import Location main = do putStrLn $loc putStrLn $loc putStrLn $loc
*Main> main (7,12) (8,12) (9,12)
Regards,
Bas
You should also take a look at the control-monad-exception package which provides, among other things, support for exception call traces (with source locations). Take a look at the description to see an example: http://hackage.haskell.org/package/control-monad-exception Regards, Bas

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On 1/19/11 17:43 , Evan Laforge wrote:
My preprocessor works well, but occasionally I do have to go in and fix yet another odd corner that came up. Initially I thought I would only simplistically replace tokens and avoid using a full syntax parser, but to really do it correctly a full parser is needed. And of course this is specific to my style (qualified imports) and project. To do this in full generality really requires the compiler's help.
Had you looked at the haskell-src-exts package? - -- brandon s. allbery [linux,solaris,freebsd,perl] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (Darwin) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iEYEARECAAYFAk06TJYACgkQIn7hlCsL25VmugCfVxD0o078PwJx7da1Axnqg2ep TzMAnR4oEkA7S1oOdYWtNiS3WBWgb88i =FbHc -----END PGP SIGNATURE-----

Evan Laforge schrieb:
One of the first things I did when starting a larger haskell project was to write a preprocessor that would replace certain tokens with (token_srcpos (filename, func_name, lineno)) and then write x and x_srcpos versions of the various 'throw' and logging functions. This is extremely handy to have, and since logs are part of my app's UI, it's part of the app itself.
I suspect that if you add source position information to 'throw' then you use 'throw' for showing (unintended) programming errors, but 'throw' is not intended for this purpose, but for expected problems like 'file not found' and so on. Adding source position to 'error', 'undefined', irrefutable patterns and logging functions for debugging makes sense to me, but for 'throw'?

On Fri, Jan 28, 2011 at 8:39 AM, Henning Thielemann
Evan Laforge schrieb:
One of the first things I did when starting a larger haskell project was to write a preprocessor that would replace certain tokens with (token_srcpos (filename, func_name, lineno)) and then write x and x_srcpos versions of the various 'throw' and logging functions. This is extremely handy to have, and since logs are part of my app's UI, it's part of the app itself.
I suspect that if you add source position information to 'throw' then you use 'throw' for showing (unintended) programming errors, but 'throw' is not intended for this purpose, but for expected problems like 'file not found' and so on. Adding source position to 'error', 'undefined', irrefutable patterns and logging functions for debugging makes sense to me, but for 'throw'?
When I say 'throw' I mean ErrorT.throwError, which boils down to returning Left or Nothing. I.e. expected errors, used simply for early return. And especially in the case of Nothing, when debugging it's important to know which particular guard failed to make the operation "expectedly" unexpectedly abort. Otherwise I wind up sticking 'trace's in strategic points to see where execution is going, which is just a pain. For the IO 'throw' srcpos info is just as important even if it's a program crashing error, so I guess I disagree no matter which 'throw' we're talking about.
participants (5)
-
Bas van Dijk
-
Brandon S Allbery KF8NH
-
Evan Laforge
-
Henning Thielemann
-
Max Bolingbroke