■
なんか、興味が散漫すぎるよな〜。そして、日記書いてる場合ではない
その発想はなかったわ
りーまんこんじぇくちゃあ
モナドって何?
なんかぐぐってたら見つけた
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/haskell/haskell-jp/368
> ところで「モナドって何」っていう質問に、なんで一言でばしっと答えら > れないんですかね。私も、分からないうちは「分かるようになったら、初 > 心者に分かりやすく教えてあげよう」なんて殊勝な事を考えていたんです > が、分かってみると「めんどくせー」になってしまいました。あるいは、 > 圏論がどうのこうのとか不必要な話で煙にまいてとか性格の悪さ全開にな > ってしまったり。モナドの呪いかなんかですかね。
プログラマにとっては、コンビネータライブラリ(高階関数)のためのデザインパターンというのはいい答なんでないかなぁと思った。
一般的にモナドって何って質問に、答えようがないのは、現実世界に殆ど似たものがない抽象概念だからじゃないかなぁ。例えば、「群って何」って聞かれた時も答えようがない気が。思いつく解答の例。
・定義を教える→そういう質問をする人に、定義教えても、だから何?としか思わないだろうし、そもそも、あの群の公理だけからでは殆ど自明なことしか言えなくて、有限群とかリー群とか制限つけない(これでも強すぎるので、もっと制限つけるのが普通)と面白いことは言えない
・具体例をあげる→しかし、個々の群の面白さが分かるには、数学の他の分野も知らないとダメな気がする。対称群とか定義だけ見ても面白そうにはみえんよなー。モナドの場合も個々の例はtrivialに近いような(少なくとも、普通にHaskellで出てくるような例では)
・代数系の一種と答える→答になってない
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ッ!おまえの命がけの行動ッ! ぼくは敬意を表するッ!
Template Haskell
・$の悲劇。普段は、return$(f 3)とかOKだけど、Template Haskellを使うと、$はspliceと解釈されてしまう。あと、リストの内包表記と、[d|..|]が似てる。GHCは色々構文無理しすぎなんじゃないだろうか。普通にスペース挟めよって話だけど
・ふむ
http://www.cse.unsw.edu.au/~chak/haskell/ghc/comm/exts/th.html
reifyDeclで、型の定義を取ってこれるらしい。今はreifyで統一されてるっぽい。型だけじゃなくて、変数や関数の定義も取ってこれたら素敵なのに。そしたら、template haskellでコンパイラ書く
reifyって聞き慣れない単語だけど、「具体化する」という意味らしい(Exicte翻訳)
Ocamlのモジュール
Ocamlとか全然知らんのだが("+."とかいう記号に耐えられなかった)、
http://itpro.nikkeibp.co.jp/article/COLUMN/20070116/258746/
を読んだら、Haskellの型クラスと一緒だな〜と思った。ということは誰かが言ってそうだけど、ファンクタってのは、なんか圏論的バックグランドがあるんだろうか。それとも偶然?
existential types
http://www.haskell.org/hawiki/ExistentialTypes
{-# OPTIONS -fglasgow-exts #-} data HsVal = forall a.HsVal a x::[HsVal] x = [HsVal 3 , HsVal "e" , HsVal getChar]
・これができて嬉しい状況って何だろう?記述量減らせることはあるかもしれんが
・型安全性は?
・なんでforallやねん