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