5

I would like to define a logger function, like

myPutStrLn = putStrLn . (++) "log: "
main = do myPutStrLn "hello"

which is fine. Now I want to format the provided String with printf, like this

myPutStrLn $ printf "test %d" (23 :: Int)

Great! Since I have this pattern very often I want to factor printf into the logger function:

myPrintf = logger . printf
  where
    -- note, this is just an example. should be
    -- replaceable with any function with this
    -- typesignature
    logger :: String -> IO ()
    logger = putStrLn . (++) "log: "

main = myPrintf "test %d" (23 :: Int)

Unfortunately, this fails with

The function `myPrintf' is applied to two arguments,
but its type `String -> IO ()' has only one
In a stmt of a 'do' block: myPrintf "test %d" (23 :: Int)
In the expression: do { myPrintf "test %d" (23 :: Int) }
In an equation for `main':
    main = do { myPrintf "test %d" (23 :: Int) }

GHC infers myPrintf :: String -> IO (), so there is obviously something wrong. I found something about Polyvariadic composition, but I'm not able to apply this to my problem. I'm not even sure if it would solve my problem.

The code is also available via gist.

lewurm
  • 1,103
  • 7
  • 19
  • If you don't mind changing the format string, you can do `printf . ("log: " ++)`. – Vitus Aug 25 '12 at 15:48
  • myLog = putStrLn . printf . ("log: " ++) does not work imo (since myLog is of type String -> IO ())- one has to replicate vararg stuff to enable this. – h_s Aug 25 '12 at 15:57
  • 2
    @h_s: Yes, but `printf . ("log: " ++) :: PrintfType c => String -> c` and there's `PrintfType` instance for `IO a`. – Vitus Aug 25 '12 at 17:19
  • @Vitus: ok i see. nice (essentially the same as the answer below) – h_s Aug 25 '12 at 19:31

1 Answers1

1

you can define your function by using hPrintf and the stdout handle.

Like that, the result of the function myPrintf remain an instance of the class HPrintfType

myPrintf::  (HPrintfType c) => String -> c
myPrintf = (hPrintf stdout) . (printf "log:%s")

main = myPrintf "test %d" (23 :: Int)

The polyvariadic form for the printf function works only because you have this instance definition :

(PrintfArg a, PrintfType r) => PrintfType (a -> r).

at each new PrintfArg parameter, type inference return an PrintfType class type if possible.

For working your logger function would have the following type :

logger :: (PrintfType c) => String -> c

but the compilator wil fail because the function return an IO () and not the more generic typeclass PrintfType.

In my opinion, only a modification of the module Text.Printf could help you because you can't create new instance of PrintfType as some method are hidden

fp4me
  • 463
  • 2
  • 8
  • nice trick, but that's not what I wanted, is it? see the comment above `logger` in my question. I want to use `printf` for formatting something to a String, and then put it somewhere into the IO world (not necessarily a handle, but for example into a global storage). – lewurm Aug 29 '12 at 10:34
  • 1
    i have updated my answer to explain why for me is not possible – fp4me Aug 29 '12 at 13:57