returnを使うときはかっこつけよう!
んで、Haskellが、考えさえまとまれば素直にコードになりそうなよさげな言語であることは確認したが、それから殆ど進んでいない自分に萎え。コードにちょっと追加したらいきなりコンパイルエラーが多発して頭を抱えていたんですが、今日になって解決したので恥を晒して置きます(--;
とりあえずコードから。ちょっと長いですが本質は1箇所です。
parsec2.hs(NG版)
module Main where
import Text.ParserCombinators.Parsec
-- 文字列配列を接続して文字列を返す
untexts :: [String] -> String
untexts =
untexts (x:xs) = x ++ untexts xs
-- トーク
talk :: Parser String
talk = do attr <- talkAttrs
talkSeparator
body <- talkBody
return ("attr=[" ++ attr ++ "] body=[" ++ untexts body ++"]")
-- トーク属性
talkAttrs :: Parser String
talkAttrs = many1 talkAttr
talkAttr :: Parser Char
talkAttr = do char '1' <|> char '2'
-- トークセパレータ
talkSeparator :: Parser ()
talkSeparator = do many space
char ':'
many space
return ()
-- トーク本文
talkBody :: Parser [String]
talkBody = do many talkToken
talkToken :: Parser String
talkToken = do try(ohMarks)
<|> do a <- anyChar
return [a]
-- びっくりマーク集合
ohMarks :: Parser String
ohMarks = do a <- many1 ohMark
return "[oh<" ++ a ++ ">]"
ohMark :: Parser Char
ohMark = do char '!' <|> char '?'
-- 実行ルーチン
run :: Show a => Parser a -> String -> IO ()
run p input = case (parse p "" input) of
Left err -> do putStr "parse error at "
print err
Right x -> print x
main = run talk "1:Hello World!!?"
SARS2あたりで!マークを検出してウェイトを入れる、って処理があるので、こちらもそれを習おうと、「talkBody」のパース部分を更に細かく入れたのですが‥‥。return "[oh<" ++ a ++ ">]"の行でエラーが出る。
実行してみると、
>runhaskell parsec2.hs parsec2.hs:48:30: Couldn't match `[Char]' against `Char' Expected type: Char Inferred type: [Char] In the expression: a ++ ">]" In the second argument of `(++)', namely `a ++ ">]"' >
[Char]が存在すべきところにCharがある?あれ?ohMark関数はCharを返して、many1関数は1以上のマッチする集合を返すんだから[Char]が返るんだよね?で、talkTokenでaは[Char]を束縛しててつまりString(文字列)なわけで、++演算子は配列の連結...Stringを繋げるんだから、「"[oh<" ++ a ++ ">]"」って間違ってないよね?あれ??
で、結論としては下記の通りに変更したら通りましたorz。
ohMarks関数正解版
ohMarks :: Parser String ohMarks = do a <- many1 ohMark return ("[oh<" ++ a ++ ">]") -- returnの引数を()で括った
JavaとかVBとか、まあとにかく関数言語じゃないところで学習してきた人間がはまりやすい罠ですが、Haskellのreturnは文ではなく、「標準ライブラリの関数」です。
return関数の定義
Prelude.return return :: a -> m a return x x を Monad 付きにする。 -- 各エントリのフルパスを返す getDirectoryContents getDirectoryContentsFull :: FilePath -> IO [FilePath] getDirectoryContentsFull dir = do entries <- getDirectoryContents dir return $ map (\ent -> dir ++ "/" ++ ent) entries
更に関数結合は最も強い結合ルールとなるので、先ほどの「return "[oh<" ++ a ++ ">]"」は、「(return "[oh<") ++ a ++ ">]"」って解釈されてしまっていたのでした。そりゃ、Monad aの値に配列をくっつけようとしてもエラーになりますね(--;。returnは単なる関数でJavaあたりと違って特別扱いされませんので、ひとつの引数を渡したい場合はちゃんと引数全体を()で括りましょう。というお話でした。
ということでこれでParseCライブラリは制覇したので(してません)、次は辞書構造のほうに挑戦してみましょうか。‥‥いつ出来るやら(--;