Haskellでprintf書くのってどうやるの?

っていう、可変長引数にも繋がるお話。

GHCのprintfのコードが長過ぎて嫌だなーという女子大生の方向けに、小さく抜き出したものが以下のコード

data UPrintf = UString String | UInt Int

my_printf :: (PrintfType r) => String -> r
my_printf fmts = spr fmts []

class PrintfType t where
    spr :: String -> [UPrintf] -> t

class PrintfArg a where
    toUPrintf :: a -> UPrintf

instance PrintfType String where
    spr fmts args = uprintf fmts (reverse args)

instance PrintfType (IO a) where
    spr fmts args = do putStrLn (uprintf fmts (reverse args))
                       return undefined

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    spr fmts args = \a -> spr fmts (toUPrintf a : args)

instance PrintfArg Int where
    toUPrintf = UInt

instance PrintfArg String where
    toUPrintf = UString

uprintf :: String -> [UPrintf] -> String
uprintf "" []                          = ""
uprintf "" (_:_)                       = fmterr
uprintf ('%':_) []                     = argerr
uprintf ('%':'d':cs) ((UInt i):res)    = (show i) ++ uprintf cs res
uprintf ('%':'s':cs) ((UString s):res) = s ++ (uprintf cs res)
uprintf (c:cs) us                      = c:uprintf cs us

perror :: String -> a
perror s = error ("print: "++s)
fmterr = perror "formatting string ended prematurely"
argerr = perror "argument list ended prematurely"
usage:
my_printf "hoge %d %s" (1::Int) "bar"


このprintf(GHCのprintfとほぼ同じものですが)が悪いお話は
http://page.freett.com/shelarcy/log/2008/diary_04.html#continuation_fest_2008
ここに書かれています。


つまり、"コンパイル時に静的に型安全であるかどうかを調べる事が出来ず、実行時にerrorが吐かれる"という事です。
例えば

main = my_printf "hoge %d %d" "bar" "fuga"

/Users/ranha/Cayenne/Paper/src% ghc -fglasgow-exts mock_printf.hs
a.out: print: formatting string ended prematurely

とこんな具合。

じゃあCayenneだとどうなりますか

っていう話。
Cayenneでのprintfを再載

-- printf.cy
module sample$print =
#include Prelude
struct
concrete
PrintfType :: String -> #
PrintfType (Nil)        = String
PrintfType ('%':'d':cs) = Integer-> PrintfType cs
PrintfType ('%':'s':cs) = String -> PrintfType cs
PrintfType (c:cs)       =           PrintfType cs

my_printf :: (fmt :: String) -> PrintfType fmt
my_printf fmt = pr fmt ""

private
pr :: (fmt :: String) -> String -> PrintfType fmt
pr (Nil)        res = res
pr ('%':'d':cs) res = \ i -> pr cs (res ++ show i)
pr ('%':'s':cs) res = \ s -> pr cs (res ++ s)
pr (c:cs)       res = pr cs (res ++ c:Nil)
--use.cy
module sample$use = 
#include Prelude
open sample$print use * in

do Monad_IO
  putStrLn (my_printf "Hello,World!!")
  putStrLn (my_printf "%d %d %s Hoge" 1 2 "Foo")
  putStrLn (my_printf "%d %d" "Foo" "Bar")
/Users/ranha/Cayenne/misc% cayenne print.cy 
/Users/ranha/Cayenne/misc% cayenne use.cy
cayenne: Compilation errors:
"use.cy", line 8, column 30, Type error: chkT
Expression
"Foo"
  has type
data Nil |
     (:)
       System$CharType.Char
       (let type List a = data Nil | (:) a (List a);
	in  List System$CharType.Char)
  but should have type
System$Integer.Integer

素晴らしい!!!


#やってる事が違うんですけどもね