えちょ記

語らないブログ

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ライブラリは制覇したので(してません)、次は辞書構造のほうに挑戦してみましょうか。‥‥いつ出来るやら(--;