可変長引数をHaskellで。
Haskellでも可変長引数はできる。らしい。
まぁ、型が死ぬので絶対オススメしないが。
ということで、printfのソースをリスペクトしつつ、色々試してみた。
とりあえず、考え方としては、
hoge 1 2 3
があったとき、hogeは一つ引数をとる"a -> b"と仮定する。
すると、
(hoge :: a -> b) 1 2 3 (hoge 1 :: b) 2 3
と引数を一つ"喰べる"ことができる。
で、このbという型がまた、同様に"a -> b"と等しくなれば、
(hoge 1 :: b) 2 3 (hoge 1 :: a -> b) 2 3 (hoge 1 2 :: b) 3 (hoge 1 2 :: a -> b) 3 (hoge 1 2 3 :: b)
と"喰べ"続けることができて、最後にbが残ると。
もちろん、"b = a -> b"は成り立たないし、無理矢理作っても、"the infinite type"と怒られる。
そこで使うのがclass。
一般的にclassは共通の性質(?)みたいなものをまとめて名前付けするもの、とか勝手に認識しているのですが、今回はちょいと変則的に(?)使っていている気がします。
自分が思ってるだけで、実はそうでないのかもしれない。
以下はGHCのPrintf.hsから一部抜粋。
class PrintfType t where spr :: String -> [UPrintf] -> t instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where spr fmts args = \ a -> spr fmts (toUPrintf a : args)
このあたりがキモ*1。
instance定義を見ると、rも(a -> r)もPrintfType classのinstanceであることが分かる。
これが結局、先のb = a -> bをやってのける小細工。
で、このままだと終わらんので、IO aもinstanceにしてしまう。
instance PrintfType (IO a) where spr fmts args = do putStr (uprintf fmts (reverse args)) return undefined
これで最後に(上のa,bの例の)最後のbがIO aとして扱われるようになると。
で、これで終わりじゃなくて、なぜかPrintfType Stringも書いてある。*2
instance PrintfType String where spr fmt args = uprintf fmt (reverse args)
で、これを書くとprintfがsprintfに進化するというね。
IOとして扱う、Stringとして扱う、それぞれで挙動が変えられるのは面白い。
が、
キモい。
ということで、以上をふまえてなんとなく書いてみた。
class IsChar c where toChar :: c -> Char fromChar :: Char -> c instance IsChar Char where toChar = id fromChar = id class VAShow v where vaShow :: [String] -> v instance VAShow Int where vaShow ss = length ss instance (IsChar c) => VAShow [c] where vaShow ss = map fromChar $ concat $ reverse ss instance VAShow (IO a) where vaShow ss = putStrLn (map fromChar $ reverse $ concat ss) >> return undefined instance (Show s, VAShow v) => VAShow (s -> v) where vaShow ss = \s -> vaShow $ (show s):ss main :: IO () main = do putStrLn $ vaShow [] (1::Int) ("hoge"::String) (1.5::Double) ('A'::Char) print $ (vaShow [] (1::Int) ("hoge"::String) (1.5::Double) ('A'::Char) :: Int) vaShow [] (1::Int) ("hoge"::String) (1.5::Double) ('A'::Char)
Stringがそのまま使えないようなので、IsCharというのを作った。というのはPrintf.hsのパクりです。
とりあえずShowなやつらをいくらでも引数として渡せる。
加えて、IOだと画面出力、Intなら引数の個数をGetできます。
ただ、型推論が死ぬので、一個一個引数の型を明示せんとWarningがでるというね。
printfはフォーマット文字列があるから、その辺は問題ないらしい。多分。