Re: [Haskell] The initial view on typed sprintf and sscanf

oleg [1]:
We demonstrate typed sprintf and typed sscanf sharing the same formatting specification.
[1]http://www.haskell.org/pipermail/haskell/2008-August/020605.html Reading Oleg's post, I noticed that it is quite straightforward to generalise printing to arbitrary output types.
class Monoid p => Printf p where printChar :: Char -> p --etc
intp :: Printf p => F a b -> (p -> a) -> b intp FChr k = \c -> k (printChar c) intp (a :^ b) k = intp a (\sa -> intp b (\sb -> k (mappend sa sb))) --etc
printf :: Printf p => F p b -> b printf fmt = intp fmt id
The Printf instances for String, ShowS, ByteStrings, and IO () are all trivial. Printing directly to a file (without building an intermediate string) is slightly more interesting:
instance Monad m => Monoid (ReaderT r m ()) where mempty = return () mappend = (>>)
instance Printf (ReaderT Handle IO ()) where printChar c = ask >>= \h -> liftIO (hPutChar h c) --etc
(<<) :: Handle -> ReaderT Handle IO () -> IO () (<<) = flip runReaderT
test_fprintf h = h << printf (lit "Hello " ^ lit "World" ^ chr) '!'
Unfortunately, I don't seem to be able to make the expected fprintf function, because printf's format-dependent parameter list makes it impossible to find a place to pass the handle. Hence the C++-like (<<) ugliness.

haskell:
oleg [1]:
We demonstrate typed sprintf and typed sscanf sharing the same formatting specification.
[1]http://www.haskell.org/pipermail/haskell/2008-August/020605.html
Reading Oleg's post, I noticed that it is quite straightforward to generalise printing to arbitrary output types.
class Monoid p => Printf p where printChar :: Char -> p --etc
intp :: Printf p => F a b -> (p -> a) -> b intp FChr k = \c -> k (printChar c) intp (a :^ b) k = intp a (\sa -> intp b (\sb -> k (mappend sa sb))) --etc
printf :: Printf p => F p b -> b printf fmt = intp fmt id
The Printf instances for String, ShowS, ByteStrings, and IO () are all trivial.
Printing directly to a file (without building an intermediate string) is slightly more interesting:
instance Monad m => Monoid (ReaderT r m ()) where mempty = return () mappend = (>>)
instance Printf (ReaderT Handle IO ()) where printChar c = ask >>= \h -> liftIO (hPutChar h c) --etc
(<<) :: Handle -> ReaderT Handle IO () -> IO () (<<) = flip runReaderT
test_fprintf h = h << printf (lit "Hello " ^ lit "World" ^ chr) '!'
Unfortunately, I don't seem to be able to make the expected fprintf function, because printf's format-dependent parameter list makes it impossible to find a place to pass the handle. Hence the C++-like (<<) ugliness.
I also wonder if we could give a String syntax to the formatting language, using -XOverloadedStrings and the IsString class. -- Don

On Mon, Sep 1, 2008 at 1:00 AM, Don Stewart
I also wonder if we could give a String syntax to the formatting language, using -XOverloadedStrings and the IsString class.
Probably easier with Template Haskell:
ghci -fth Sprintf.hs *Sprintf> :t $(sprintf "Hello %s, showing %S and %S") $(sprintf "Hello %s, showing %S and %S") :: (Show a1, Show a) => [Char] -> a -> a1 -> [Char] *Sprintf> $(sprintf "Hello %s, showing %S and %S") "Don" 1 (5,6) "Hello Don, showing 1 and (5,6)"
Code follows, which could be ported to use the printf/scanf language Oleg defined. -- ryan {-# LANGUAGE TemplateHaskell #-} module Sprintf where import Language.Haskell.TH data SprintfState = SprintfState String (ExpQ -> ExpQ) flush :: SprintfState -> (ExpQ -> ExpQ) flush (SprintfState "" k) = k flush (SprintfState s k) = (\e -> k [| $(litE $ StringL $ reverse s) ++ $e |]) finish :: SprintfState -> ExpQ finish (SprintfState s k) = k (litE $ StringL $ reverse s) addChar :: Char -> SprintfState -> SprintfState addChar c (SprintfState s e) = SprintfState (c:s) e addCode :: ExpQ -> SprintfState -> SprintfState addCode k s = SprintfState "" (\e -> flush s $ [| $k ++ $e |]) sprintf' :: SprintfState -> String -> ExpQ sprintf' k ('%':'S':cs) = [| \x -> $(sprintf' (addCode [| show x |] k) cs) |] sprintf' k ('%':'s':cs) = [| \s -> $(sprintf' (addCode [| s |] k) cs) |] sprintf' k ('%':'%':cs) = sprintf' (addChar '%' k) cs sprintf' k (c:cs) = sprintf' (addChar c k) cs sprintf' k [] = finish k sprintf :: String -> ExpQ sprintf = sprintf' (SprintfState "" id)

Hello Ryan, Monday, September 1, 2008, 12:16:46 PM, you wrote: of course this may be done with code generation tools (such as TH). point of this research is to do this using type abilities of Haskell Don, i think this should be impossible with IsString since the point is that Haskell compiler should know types at compile time. IsString can't convert "%d" into (X int) while converting "%s" into (X String)
On Mon, Sep 1, 2008 at 1:00 AM, Don Stewart
wrote: I also wonder if we could give a String syntax to the formatting language, using -XOverloadedStrings and the IsString class.
Probably easier with Template Haskell:
ghci -fth Sprintf.hs *Sprintf>> :t $(sprintf "Hello %s, showing %S and %S") $(sprintf "Hello %s, showing %S and %S") :: (Show a1, Show a) => [Char] -> a -> a1 -> [Char] *Sprintf>> $(sprintf "Hello %s, showing %S and %S") "Don" 1 (5,6) "Hello Don, showing 1 and (5,6)"
Code follows, which could be ported to use the printf/scanf language Oleg defined.
-- ryan
{-# LANGUAGE TemplateHaskell #-} module Sprintf where import Language.Haskell.TH
data SprintfState = SprintfState String (ExpQ -> ExpQ)
flush :: SprintfState -> (ExpQ -> ExpQ) flush (SprintfState "" k) = k flush (SprintfState s k) = (\e -> k [| $(litE $ StringL $ reverse s) ++ $e |])
finish :: SprintfState -> ExpQ finish (SprintfState s k) = k (litE $ StringL $ reverse s)
addChar :: Char ->> SprintfState -> SprintfState
addChar c (SprintfState s e) = SprintfState (c:s) e
addCode :: ExpQ ->> SprintfState -> SprintfState
addCode k s = SprintfState "" (\e -> flush s $ [| $k ++ $e |])
sprintf' :: SprintfState -> String -> ExpQ sprintf' k ('%':'S':cs) = [| \x -> $(sprintf' (addCode [| show x |] k) cs) |] sprintf' k ('%':'s':cs) = [| \s -> $(sprintf' (addCode [| s |] k) cs) |] sprintf' k ('%':'%':cs) = sprintf' (addChar '%' k) cs sprintf' k (c:cs) = sprintf' (addChar c k) cs sprintf' k [] = finish k
sprintf :: String ->> ExpQ
sprintf = sprintf' (SprintfState "" id) _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
-- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

On Mon, Sep 1, 2008 at 1:30 AM, Bulat Ziganshin
of course this may be done with code generation tools (such as TH). point of this research is to do this using type abilities of Haskell
Yes, I know. My point was that TH could be used as a minimal "String -> ExpQ" wrapper, where the ExpQ is the code used by this library, to save keystrokes. I'm all about writing 100 lines of code to save 10 keystrokes per use of a function. :)
Don, i think this should be impossible with IsString since the point is that Haskell compiler should know types at compile time. IsString can't convert "%d" into (X int) while converting "%s" into (X String)
Although of course you could use IsString to convert raw strings into 'lit "string"'; this would make the syntax slightly more palatable:
printf ("hello" ^ char ^ "world" ^ char) ' ' '!'
-- ryan

Matthew Brecknell wrote:
Unfortunately, I don't seem to be able to make the expected fprintf function, because printf's format-dependent parameter list makes it impossible to find a place to pass the handle. Hence the C++-like (<<) ugliness.
How about this:
fprintf :: Handle -> F (IO ()) b -> fprintf h fmt = write fmt id where write :: F a b -> (IO () -> a) -> b write (FLit str) k = k (hPutStr h str) write FInt k = \i -> k (hPutStr h (show i)) write FChr k = \c -> k (hPutChar h c) write (FPP (PrinterParser pr _)) k = \x -> k (hPutStr h (pr x)) write (a :^ b) k = write a (\sa -> write b (\sb -> k (sa >> sb)))
*PrintScan> fprintf stdout fmt5 15 1.3 '!' abc15cde1.3!*PrintScan> The first thing I did last night was change String to type ShowS = String -> String :
intps :: F a b -> (ShowS -> a) -> b intps (FLit str) k = k (str++) intps FInt k = \x -> k (shows x) intps FChr k = \x -> k (x:) intps (FPP (PrinterParser pr _)) k = \x -> k (pr x ++) intps (a :^ b) k = intps a (\sa -> intps b (\sb -> k (sa . sb)))
sprintfs :: F ShowS b -> b sprintfs fmt = intps fmt id
Ideally PrinterParser would display using "ShowS" as well:
data PrinterParser a = PrinterParser (a -> ShowS) (String -> Maybe (a, String))
Or one could use instance witnesses via GADTs to wrap up Show:
data F a b where FSR :: (Show b,Read b) => F a (b -> a)
But I think changing PrinterParser would result in simpler code. Cheers, Chris
participants (5)
-
Bulat Ziganshin
-
ChrisK
-
Don Stewart
-
Matthew Brecknell
-
Ryan Ingram