Haskellにもscanfを

Haskellでも、scanfが欲しいぜという意見をどっかで見た。まあ確かに、"2007/01/18"とかを、(2007,01,18)に分解するときにParsecを使うとかは、オーバーキルな感が。いや、そういうときは、lex使うんだよ!Preludeにはいってる。

Prelude> lex "2004/03/12"
[("2004","/03/12")]
Prelude> lex "/03/12"
[("/","03/12")]

これ凄い使いづらいんだけど、何でこんな意味不明にリストにしてるんだろう。さて、というわけで、lexでいいんですが、scanfを書こう。まあ、scanfの挙動を完全に再現するのは大変なので、そのへんは臨機応変にscanfっぽいものを

module Scanf(sscanf) where
import Text.ParserCombinators.Parsec
import Data.Maybe
import Text.Printf
import Text.Read.Lex

--defined in Text.Printf,but not exported
data UPrintf = UChar Char | UString String | UInt Int | UInteger Integer | 
               UFloat Float | UDouble Double deriving Show 
--R:raw text
data PrintfFmt = C | D | O | X | U | F | G | E | S | R String  deriving Show

parseFmt :: String -> IO [PrintfFmt]
parseFmt fmtString = 
  case (parse p "" fmtString) of
   Left err -> fail "illegal format"
   Right x -> return x
 where
  p = many (p1 <|> p2)
  p1 = do{char '%';
          x <- oneOf "cdoxufges";
          return (charToFmt x)}
  p2 = do{s<-many1 (satisfy$not.('%'==));
          return (R s)}
  charToFmt c = fromJust$lookup c fmtTable
  fmtTable = [('c',C),('d',D),('o',O),('x',X),('u',U),('f',F),('g',G),('e',E),('s',S)]

scanfParser :: [PrintfFmt] -> Parser [UPrintf]
scanfParser [] = return []
scanfParser (R s:xs) =
 do{
  s <- string s;
  rest <- (scanfParser xs);
  return rest}
scanfParser (C:xs) =
 do{
  c <- letter;
  rest <- (scanfParser xs);
  return $(UChar c):rest
 }
scanfParser (D:xs) =
 do{
  n <- many1 digit;
  rest <- scanfParser xs;
  return (UInt ((read n)::Int):rest)}
scanfParser (S:xs) =
 do{
  s <- many1 letter;
  rest<- scanfParser xs;
  return (UString s:rest)}

sscanf :: String -> String -> IO [UPrintf]
sscanf input fmtString = 
 (parseFmt fmtString)>>=(\fmt ->
    case (parse (scanfParser fmt) "" input) of
     Left err -> fail "illegal input of sscanf\n"
     Right x  -> return x)

こんな感じでの使用を想定

*Scanf> do{[a,b,c] <- sscanf "2005/12/02" "%d/%d/%d" ;print (a,b,c)}
(UInt 2005,UInt 12,UInt 2)

まあ、template Haskell使わないと、これが限度で、[UPrintf]ってのも値取り出すのめんどくさいよなー。


既知の問題点
・"%%"が使えない
・%dはIntegerとして読み込まれる(のはむしろ利点な気もするが)
・%sが"letter"にしかマッチしない
・scanset(%[...]みたいなの)が使えない
・c,d,s以外のフォーマットを無視
・フォーマット中の空白は、任意の長さにマッチすべきだけど、そうなってない
・%2dとか%3sみたいなのが使えない
・その他scanfと挙動が違うとこがあるかも


まあ、大体は、ちょっとコード追加するだけで対応できるような。で、これは返り値が[UPrint]なのがださい。そのへんを解決するためにtemplate Haskellでscanfを書く

genScanfParser :: [PrintfFmt] -> ExpQ
genScanfParser ls =
 do{varlist<-mkVarlist ls;
    return $ DoE $ 
     (mkBinds varlist)++
     [NoBindS (AppE (VarE (mkName "return")) (TupE (mkRets varlist)))]}
  where
   mkVarlist [] = return []
   mkVarlist (x:xs) = 
    do{rest <- mkVarlist xs;
       case x of 
        (R _)-> return (Nothing:rest)
        otherwise->do{n<-newName "c";return ((Just n):rest)}}
   parserList = map toParser ls
   mkBinds vlist = map mkBind (zip vlist parserList)
   mkBind (Just var , parser) = (BindS (VarP var) parser)
   mkBind (Nothing , parser) = (NoBindS parser)
   mkRets vlist = [mkRetVal(c,var)| (c,Just var) <- (zip ls vlist)]
   mkRetVal (c,var) = case c of 
    C -> (VarE var)
    D -> SigE (AppE (VarE (mkName "read")) (VarE var)) (ConT (mkName "Integer"))

toParser c = 
 case c of
  C -> (VarE (mkName "Scanf.scanfParserC"))
  D -> (VarE (mkName "Scanf.scanfParserD"))
  S -> (VarE (mkName "Scanf.scanfParserS"))
  (R s) -> (AppE (VarE (mkName "Scanf.scanfParserR")) (stringToExpr s))
 where
  stringToExpr s = ListE [LitE (CharL c)|c<-s]

scanfParserC = do{c<-letter;return c;}
scanfParserD = do{c<-many1 digit;return c;}
scanfParserS = do{s<-many1 letter;return s} 
scanfParserR str = do{s<-string str;return s}

scanf fmtString = 
 [|do{ln<-getLine;
      case (parse $p "" ln) of
       Left err -> fail "scanf parser error:bad input"
       Right x -> return x} |]
  where
   p= join $ runIO $ (parseFmt fmtString)>>=(return.genScanfParser)

使い方は、

{-# OPTIONS -fth #-}
module Main where
import Scanf

main = do{(x,y) <- $(scanf "%d/%d");print x}

基本的には、パーサをコンパイル時に生成しているだけなので、何をやってるかは"runQ(scanf "%d/%d")>>=putStrLn.pprint"とかやれば、すぐ分かるような。と言いつつ三ヵ月後の自分が読めない気がしなくもない。とりあえず、trivialなケース以外では、[| ..|]とか使わず、地味に、構文木構築していく方がいいと思った。というか、それでえらい罠にはまった。newNameの返す型がQ Nameなので非常にうっとうしい(やむをえないことだが)。あと、VarEとか型構成子の"monadic"版varEとかいらん気も


それから、HaskellのprintfはTemplate Haskell使ってるのかと思ってたら、そうではなかった。なんか型クラスを非常にうまく使ってるっぽい(多分)。これは一度読むべき(いや、読んでも役に立たないと思うが)


Template Haskell使わずにprintfを書くッ!
Lennartッ!おまえの命がけの行動ッ! ぼくは敬意を表するッ!