Haskellにも喋ってもらおう(2)
数日前にTVを見ていたら、鉄分を含んだ「鉄子さん」の紹介をしていた。サプリメントで 取る、CaやFeの話ではなくて、鉄道大好きな女性の事だ。男の領分に進出し、親父 ぽくなったのかな? そう言えば、「歴女」もブームだなあ。
いままででは考えられないような趣味に、女性が進出してきているが、物を集めたり する趣味はどうなんだろう? ひたすら、デッシュ(パラボラアンテナ)や「こま犬」 の写真を撮り続けている、あの人の領域は侵されるのだろうか?
飛行機の写真をひたすら撮り続ける人が、今度は、変電所にはまって、写真に収め 始めたようだ。見せてもらったけど、にょきにょきと突き出た碍子が、うにの棘に 見えてしまう私は、そういう世界とは縁遠いのだろうか?
暇にまかせて、近くにある大送電線や変電所の写真でも撮りに行ってみようか。但し、 こっそり取らないと、テロリストに間違えられて、通報される恐れがありそうなので 要注意。
ひらがなへの変換
さて、いよいよ漢字混じりの文章を、ひらがなに変換する関数を書いていく。これって 結局、ルビを振る事だよね。関数名は、ruby としよう。だって、htmlのタグに ruby ってのがあるんだもの。(えっ、趣味悪いって? まあ、こういう事でもないと、haskell に、rubyの名前を登場させられませんから)
ruby :: String -> IO String ruby s = do (input, output, _, _) <- runInteractiveProcess "mecab" ["--eos-format=" ,"--unk-format=\\s%m" ,"--node-format=\\s%pS%f[7]"] Nothing Nothing hPutStr input s hClose input hGetContents output
書けたら、その場でチェックですね。
*Main> ruby "hellow world 123times" " hellow world 123 times"
どうやら、大丈夫そうですんで、speakに組み込んで実験します。旨く行くかな?
期待したんですが、jsayが、酷いエラーを吐いてしまいました。どのくらい酷いかと言うと M$に言いつけてやると言う程、酷いエラー。今までちゃんと動いていた、データでも だめなんで、どんなデータが jsay に渡っているかプローブしました。
jsay :: String -> IO ExitCode jsay s = do { putStrLn s; rawSystem "jsay" [s] }
Haskellでも、progn みたいな事が出来るんですね。こうしておいてから
c:\sakae\gsay>ghc --make speak.hs [1 of 1] Compiling Main ( speak.hs, speak.o ) Linking speak.exe ...
コンパイルしました。そして実行してみると、やはり、酷いエラー。但し、jsayに 渡ったデータが見れます。それによると、必ず、行頭に1個のスペースが入っていました。 後は、問題なく、ルビを振ってくれています。しょうがないので、 AquesTalkのサポートページを見たら、先頭に半角スペース が有ると、落ちると報告が出ていました。折を見て直すそうです。
そっか、それじゃ、何とかせねば。。。 取り合えずの逃げとして
speak :: String -> IO ExitCode speak s = do r <- ruby s utf <- s2u r sjis <- u2s $ unwords $ words utf jsay sjis
のように、wordsの特性を利用して、先頭スペースをカットしました。どうせ、この 部分は、変更しますんで。
Prelude> let s = " abc 123 def " Prelude> words s ["abc","123","def"] Prelude> unwords $ words s "abc 123 def"
これで、ようやく漢字混じり文を、読み上げるようになりました。
次はアルファベットや数字混じり文も喋らせる
これが出来れば、外交にも財政にも強い事を国民のみなさんに強くアッピール出来る ので、選挙には必須のアイテムになります。上記を、早速改造しました。 (ソースは、巻末参照)
joinが思い出せなくて、カンニングしちゃいました。なかなか特殊なモジュールに 入っていて、探すのに苦労しちゃいました。joinとかsplitぐらいは、標準に入れて おいて欲しいぞ。また、ascpの中で、head s を何回も評価してて、ちょっと無駄っぽい。 綺麗な書き方って、ないものだろうか?
今日は980円の靴を買いに、ABC靴店へ行きました。 売り切れで、残念無念です。
旨く、喋るかな?
c:\sakae\gsay>ghci speak.hs GHCi, version 6.10.3: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer ... linking ... done. Loading package base ... linking ... done. [1 of 1] Compiling Main ( speak.hs, interpreted ) Ok, modules loaded: Main. *Main> :main smpl.txt Loading package syb ... linking ... done. Loading package base-3.0.3.1 ... linking ... done. Loading package bytestring-0.9.1.4 ... linking ... done. Loading package old-locale-1.0.0.1 ... linking ... done. Loading package old-time-1.0.0.2 ... linking ... done. Loading package filepath-1.1.0.2 ... linking ... done. Loading package Win32-2.2.0.0 ... linking ... done. Loading package directory-1.0.0.3 ... linking ... done. Loading package process-1.0.1.1 ... linking ... done. Loading package array-0.2.0.0 ... linking ... done. Loading package random-1.0.0.1 ... linking ... done. Loading package haskell98 ... linking ... done. Loading package utf8-string-0.3.5 ... linking ... done. キョウ,ハ,<NUMK VAL=980>,エン,ノ,クツ,ヲ,カイ,ニ,、,H ウリキレ,デ,、,ザンネン, *Main>
ザンネン、1行目は喋ってくれませんでした。どうも、ABCの所がまずいようです。 何故なんだ。ちょっとemacs上で実験してみます。
*Main> tr "123 ABC" "<NUMK VAL=123>,\12360\12540,\12403\12540,\12375\12540" *Main> U.putStrLn $ tr "123 ABC" <NUMK VAL=123>,えー,びー,しー
これは、大丈夫そう。utf-8の日本語領域って、文字列表現では、数字?になるのね。 知らなかった。それじゃ、こっちは?
*Main> tr "abc くつ" "\12360\12540,\12403\12540,\12375\12540,\32314\19978\9661" *Main> U.putStrLn $ tr "abc くつ" えー,びー,しー,縺上懿。
文字化けしてるよ。何故? GHCのバグでしょうか?外から来たものと、リテラルは 混ぜるな危険とかいう制約があるのでしょうか? よう分かりませんです。 秋に出ると言う、新版に期待しますか。一応、全リストを載せておきます。
-- -*- utf-8 -*- -- speak japanese (use external application iconv,mecab,jsay) -- Usage: speak.exe shift-jis-file import System import System.IO import System.Process import System.Cmd import List import Data.Char import qualified System.IO.UTF8 as U type Table = [(Char,String)] tblAsc :: Table tblAsc = [('a',"えー"), ('b', "びー"), ('c', "しー"), ('d', "でー")] tblPunct :: Table tblPunct = [('(',"かっこ"), (')',"こっか"), ('-', "だっつしゅ") ,('<',"しょうなり"), ('>',"だいなり"), (':',"ころん")] a2p :: Char -> Table -> String a2p c t = case lookup c t of Just p -> p Nothing -> "しらないもじ" join :: [a] -> [[a]] -> [a] join delim l = concat (intersperse delim l) iconv :: String -> String -> String -> IO String iconv from to s = do (input, output, _, _) <- runInteractiveProcess "iconv" ["-f", from, "-t", to] Nothing Nothing hSetBinaryMode input False hSetBinaryMode output False hPutStr input s hClose input hGetContents output s2u :: String -> IO String s2u s = iconv "SHIFT_JIS" "UTF-8" s u2s :: String -> IO String u2s s = iconv "UTF-8" "SHIFT_JIS" s jsay :: String -> IO ExitCode jsay s = do { putStrLn s; rawSystem "jsay" [s] } ruby :: String -> IO String ruby s = do (input, output, _, _) <- runInteractiveProcess "mecab" ["--eos-format=" ,"--unk-format=\\s%m" ,"--node-format=\\s%pS%f[7]"] Nothing Nothing hPutStr input s hClose input hGetContents output speak :: String -> IO ExitCode speak s = do r <- ruby s utf <- s2u r sjis <- u2s $ tr utf jsay sjis tr :: String -> String tr s = join "," $ map ascp $ words s ascp :: String -> String ascp s | (ord $ head s) > 127 = s | isDigit $ head s = "<NUMK VAL=" ++ s ++ ">" | isAlpha $ head s = join "," $ map (\c -> a2p (toLower c) tblAsc) s | isPunctuation $ head s = join "," $ map (\c -> a2p c tblPunct) s | isSymbol $ head s = join "," $ map (\c -> a2p c tblPunct) s | isControl $ head s = "どくもじ" | otherwise = "おかしいぞ" main :: IO () main = do args <- getArgs cs <- readFile $ head args mapM_ speak $ lines cs