可変長引数を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はフォーマット文字列があるから、その辺は問題ないらしい。多分。

*1:肝であり、キモいところ。

*2:Stringをそのまま書くとなんかエラーになる。-XFlexibleInstancesフラグをつけると通るのだが、よぅわからん。