hugs -> ghc
昼飯も食べたし、ソファーでしばし昼寝の至宝の時間。女房はと言うと、相変わらずコロンボやら タモリの世にも不思議な話やらの撮り溜めしたビデオの消化。
が、突然、テレビのリモコンが言う事を聞かなくなった。(言う事じゃなくて、ボタンの押し下げ だろうが)壊れたとぬかす。CMスキップが出来ないらしい。電池でも無くなったんでは? と返答して昼寝続行。
やっぱりリモコン壊れてる。電池換えても駄目とか。TVの説明書にリモコンが効かない項目 あるだろうから、調べてみれ。オイラーはTVなんて全く興味無いので、人事モードですよ。
大した事書いてないのね。受光部が(置物等で)塞がれていませんか? 電池へたっていませんか? 素人にどうやって電池のへたりを判断しろと。
あーあ、まだ保障期間内のはずだから、ヤマダ君の所へ行くって言い出して、保証書探し。 都合のいい解釈してて、めーかー保障1年とヤマダ君が付けてくれた5年で、6年分に なると思ってたらしい。しっかりと期限が2015年11月末って書いてあるじゃん。購入日は、 2010-11-02だった。ヤマダ君タイマー発動中って事かな。
おおまかに故障箇所を特定するんだ。本体受光部が悪いかリモコンが悪いか? それによって、 年を越せるか越せないか決まるんで、オイラーもプチ真剣モードに突入。
ぐぐったら、TV側がハングしてる可能性があるので(なにせ、Linuxが動いていますから)、 コンセントを抜いて数分してから動かしてみましょう。なるほどコールドスタートだな。
リモコンの制御CHは3種あるので、ちゃんと指定したものになってますか? どうやって調べろと? DVDプレーヤーとかエアコンのリモコンで動くか調べろ? 不親切だからパス。
次はリモコンから赤外線が出てるか確認しましょう。携帯とかデジカメのカメラを通すと リモコンが発する赤外線LEDが光るか確認出来ますってさ。
やってみた、手元にあるipadで。全く光らんぞ。やっぱりヤマダ君にお願いかな。冷や汗 出てきますよ。
まてまて、昔の流儀で、自分で一から確認してみろって声が聞こえてきた。(昔、オイラーは、 とある機械の修理を生業にしてたのさ。電話相談で、ユーザーにいろいろやってもらって、 駄目なら出張ってパターン。現場に到着して、まずはユーザーにやってもらった事を、 自分でおさらい。これで不良箇所を特定出来る事が多々あった。)
で、電池を再度入れ替え。おまけに電池フォルダー内で電池をグリグリ。リモコンが復活 したよ。あーーーーー良かった。きっと電極に不活性膜でも出来ていたんでしょう。
で、参考に赤外線LEDの光かたを拝んでおくか。再びipadに登場願った。やはり光ってないぞ。 歴代 iPhone / iPad カメラの赤外線感度を横並びで調べてみた によると、ちゃんと反応するらしいが。こういう時は、測定器を変えてやってみるのが 正しい対処法。
女房のパッカン携帯で確認。やっぱり光らんぞ。でも、ちゃんとリモコン使えてるんで、 赤外線は出ているはず。諦めきれないので、オイラーのパッカン携帯でも確認。
えと、どうやってカメラモードにするんだったかな? 何せ、オイラーは昔からの カメラ嫌いで、携帯のカメラなんて使った事無いぞ。ipadのカメラも使う事無し。
どうにかこうにかカメラを起動してみた。リモコンのボタンを押すとくっきりと 発光部が白く光りましたよ。って、事はオイラーの携帯は透けて写るんか。来年の夏が 楽しみ。(オイオイ)この際だから、家中のリモコンの発光具合を確認しとけ。 それが、まさかの時のレファレンスになるから。
それにしても、カメラの用途が赤外線LEDの確認だけとは、ちと寂しい。 あのLispの神様は、 ハッカーの遺言状 を書かれておられるんだから、オイラーは飛び切り上等な遺影ぐらいは取っておけよ。
haskell-mode
fedoraにghcなんてのを入れたものだから、それ用にemacsの環境を整えておこうと思った。 で、ネットをさ迷って設定を見つけてきたんだけど、起動すると、haskell-modeの設定が されていないと文句を垂れる。何故? しばし格闘して、hugsがhaskellだと認識してる っぽいのを発見。
設定をいじってもいいんだけど、今後の事を考えてコードには触れない事にしよう。 その代わりhugsをhaskell-modeが知らないアプリ名にしちゃえばいいんだな。そんな風に 考えて、hugsをohugsに改名した。
;; haskell-mode (autoload 'haskell-mode "haskell-mode") (autoload 'haskell-cabal "haskell-cabal") (add-to-list 'auto-mode-alist '("\\.hs$" . haskell-mode)) (add-to-list 'interpreter-mode-alist '("runghc" . haskell-mode)) (add-to-list 'interpreter-mode-alist '("runhaskell" . haskell-mode)) (setq haskell-program-name "/usr/bin/ghci") (add-hook 'haskell-mode-hook 'inf-haskell-mode) (defadvice inferior-haskell-load-file (after change-focus-after-load) "Change focus to GHCi window after C-c C-l command" (other-window 1)) (ad-activate 'inferior-haskell-load-file) (add-hook 'haskell-mode-hook 'turn-on-haskell-indent)
上記で、C-c C-l すれば、勝手に画面が割れてghciが起動してくれるんだけど、ghci側の 補間が効かないというか不完全。そんな訳でghci用にshell端末を一つ用意する事にした。 shellってのとansi-termってのは知ってたけど、ちと癖がある。そこでググル先生に 聞いてみたら、 Emacs のシェルモード比較 - shell、ansi-term、multi-term を紹介された。
multi-termの評判が良さそうなので、入れてみた。multi-termを起動して、そこからghciを 起動。そうしておいて、目指すファイルをロードすると、便利に動く。そして裏では、ファイル が開かれている。これ、なかなか便利。暫く使ってみる。
CSVパッケージ
久しぶりのHaskellで何を書こう? 例に血圧データが何年分も溜まっている事だし、 それを月毎に集計して血管の劣化具合でも観察してみるか。
えと、どんな風にするか。CSVファイルを一気読みしておいて、後は月毎にデータを 拾い出し、統計関数かな。
それにはCSVファイルを読んでhaskellのメモリー上に展開しないとな。きっとそれ用の パッケージが有るはず。
[sakae@fedora bld]$ cabal list csv
すると色々出てくるけど、シンプルな名前のやつがいいな。
[sakae@fedora bld]$ cabal info csv : Description: CSV loader and dumper This library parses and dumps documents that are formatted according to RFC 4180, \"The common Format and MIME Type for Comma-Separated Values (CSV) Files\". This format is used, among many other things, as a lingua franca for spreadsheets, and for certain web services. : Modules: Text.CSV
取りあえず入れてみた。
[sakae@fedora bld]$ ghci Prelude> :m Text.CSV Prelude Text.CSV> :bro type CSV = [Record] type Field = String type Record = [Field] csv :: Text.Parsec.String.Parser CSV parseCSV :: FilePath -> String -> Either Text.Parsec.Error.ParseError CSV parseCSVFromFile :: FilePath -> IO (Either Text.Parsec.Error.ParseError CSV) parseCSVTest :: String -> IO () printCSV :: CSV -> String
どうやらparseCSVFromFileが手軽に扱えるみたいだ。
Prelude Text.CSV> parseCSVFromFile "current.csv" Loading package transformers-0.3.0.0 ... linking ... done. : Loading package csv-0.1.2 ... linking ... done. Right [["15112821","116","71","65"],,,,]
色々なパッケージをロードしてるって事は、hugsでは動かんとな。これはまずい。現代人の 便利生活に慣れちゃったらアカンで。でも、参考にコードを見ておくか。
[sakae@fedora ~]$ cd .cabal/packages/hackage.haskell.org/csv/0.1.2/ [sakae@fedora 0.1.2]$ ls csv-0.1.2.tar.gz csv-0.1.2.tar.gz.etag [sakae@fedora 0.1.2]$ tar zxvf csv-0.1.2.tar.gz -C /tmp csv-0.1.2/ csv-0.1.2/Setup.hs csv-0.1.2/csv.cabal csv-0.1.2/COPYING csv-0.1.2/Text/ csv-0.1.2/Text/CSV.hs
パッケージになっちゃうと、ソースが見えないので、こうやって展開して見ておくのさ。 それがOSSの醍醐味!!
自前でCSV読み込み
上記のコードを参考にしつつ、hugsのPreluede.hsを漁って、自前で実装。例によって エラーチェックとは無しね。だって、正等なCSVて事が証明されているから。
-- bld tois :: [String] -> [Int] tois s = map (\x -> read x :: Int) s toi :: [[String]] -> [[Int]] toi s = map tois s csvRead :: IO [[Int]] csvRead = do sc <- readFile "current.csv" return (toi (map sepCm (lines sc))) isCmm :: Char -> Bool isCmm c = c == ',' sepCm :: String -> [String] sepCm s = case dropWhile isCmm s of "" -> [] s' -> w : sepCm s'' where (w,s'') = break isCmm s' ana :: [[Int]] -> Int -> IO () ana cs n = do putStrLn $ show $ length cs putStrLn $ show n main :: IO () main = do cs <- csvRead ana cs 1511
実験的に書いたコード。このコードを書いてて、型の宣言が自分の考えを整理する良い きっかけとなる事を実感。
mainでは、csvファイルからデータを読み込み、そのデータと解析を始める年月を解析機関へ 預けるって表現。返り値は無し。
csvReadは、汚れたリストのリストを返す。main内で、一時的にその汚れがクリーニングされる って事だな。楽しいH本の162ページあたりに説明が有った。 ファイルを一気読みして、linesで行毎に分解。そいつをsepCmを使って、カンマで分解して、 文字の配列にしる。そして、それらをmapでまとめる。最後に文字列をtoiで数値に変換。 ふぅー、疲れるわい。
カンマで分解は、ずばりの関数が無いので、wordsってのをパクッてきた。文字列を スペースで分解するやつが原本。ちょいと工夫すれば、一般化できそう。
文字列を整数に直す方法が思いつかなくて、プログラミングHaskellを参照。それがヒントに なって別バージョンを思い付いたぞ。
csvRead :: IO [[Int]] csvRead = do al <- readFile "current.csv" return (map (\e -> read ("["++e++"]") :: [Int]) (lines al) )
カンマで分解なんて面倒な事はやっていない。逆に、カンマ区切りの文字列の両端に鍵括弧を 付けたし、それをhaskellにエバッて貰おうって寸法。こちらの方が鮮やかな手口だな。
調子こいて、タプルに変換出来るかと思ったら、さすがにそれは許されなかった。 自前で定義すれば出来そうだけど、そこまで頭が回らない。
Haskellでenum
これって、C語で言う列挙型だな。使いたかったのよ。CSVデータは、測定日(yymmddhh形式)、最高血圧、、って 具合に並んでいるんだけど、それをアクセスするのに、0とか1って言うインデックス番号 もどきで設定したくないからね。ああ、対象はリストだけど、list!!n ってやれば、配列 もどき的に、データを取り出せる。
調べてみた。Haskell 代数的データ型 超入門 に説明が有った。基本的な事なのね。早速応用。
data Field = Date | Hi | Low | Pulse deriving (Enum, Show) -- Usage: am Hi cs am :: Field -> [[Int]] -> [Int] am k cs = [x!!(fromEnum k) | x <- cs, x!!0 `mod` 100 < 12]
こんな具合に、ユーザーに優しい使い方が出来、かつロバスト(頑丈)なプログラムが 実現出来る。
一つ気になった事がある。Enumの初期値が、どうもZERO固定のようだ。これを任意に 設定する方法ってあるのだろうか?
数値で悩む
平均を取る時に、合計を母数で割り算する。えと、割り算は / だな。で、やってみると
Prelude> let x=10::Int Prelude> let y=3::Int Prelude> x / y <interactive>:4:3: No instance for (Fractional Int) arising from a use of ‘/’ In the expression: x / y In an equation for ‘it’: it = x / y
こんな具合に、あざけりを受ける。じゃ、てんで
Prelude> x `div` y 3
divを使うと、切捨てが起きて、よい方向に行ってしまいます。Haskellって難しい事を 簡単に行えて、易しい事がやたら小難しいって誰か言ってたけど、実感します。 まあ、数学の道具ですから。。。 悩んでいてもしょうがないので、ぐぐったら Haskellにおける数値間の変換 に出会った。RWH本持ってるんで、灯台本暗しである。
Prelude> (fromIntegral x) / (fromIntegral y) 3.3333333333333335
結果を表形式にしたいんだけど、上記みたいに無意味な桁数を表示されちゃ美しくない。 せいぜい、少数点以下1桁もあれば十分。はて、ghcに桁を丸めて表示する関数なんて 有ったっけ?
きっとモジュールをimportすれば可能だろうけど、それじゃhugsは無視された事になる。 コードを無修正でhugsで動かすってポリシーが崩れる。 そこで、ちらちらとhugsについてたPreludeを見て、自前で組み立てる事にした。
使えそうな道具は、リストをある所で分割してくれるって関数。 span。
span, break :: (a -> Bool) -> [a] -> ([a],[a]) span p [] = ([],[]) span p xs@(x:xs') | p x = (x:ys, zs) | otherwise = ([],xs) where (ys,zs) = span p xs'
ある所ってのが、少数点の有る所って事にすれば、整数部と少数部に分割出来る。 ちと、ghciで実験。昔irbをよく使ってたのを思い出すよ。replは必需品。
Prelude> span (\x -> x /= '.') "123.456" ("123",".456")
結果はタプルで返ってくるんで、fstで全部、sndは2文字分takeすればいいな。
上のfromIntegralを使って割り算をした場合、割り切れても、少数点以下には0が付く。 もっと多数桁が欲しい場合は、replicateを使って、足りない0を付け足してあげれば良い。
hugsのPrelude
は、宝の宝庫。じっくり見ておくと御利益が有るぞ。
107:-- Standard value bindings {Prelude} ----------------------- 123:-- Equality and Ordered classes ---------------------------- 157:-- Numeric classes ----------------------------------------- 279:-- Numeric functions --------------------------------------- 317:-- Index and Enumeration classes --------------------------- 356:-- Read and Show classes ----------------------------------- 389:-- Monad classes ------------------------------------------- 422:-- Evaluation and strictness ------------------------------- 429:-- Trivial type -------------------------------------------- 460:-- Boolean type -------------------------------------------- 478:-- Character type ------------------------------------------ 545:-- Maybe type ---------------------------------------------- 564:-- Either type --------------------------------------------- 573:-- Ordering type ------------------------------------------- 578:-- Lists --------------------------------------------------- 608:-- Tuples -------------------------------------------------- 613:-- Standard Integral types --------------------------------- 792:-- Standard Floating types --------------------------------- 1016:-- Some standard functions --------------------------------- 1057:-- Standard functions on rational numbers {PreludeRatio} --- 1137:-- Standard list functions {PreludeList} ------------------- 1341:-- PreludeText --------------------------------------------- 1571:-- Exception datatype and operations ----------------------- 1715:-- Monadic I/O: --------------------------------------------- 1825:-- Hooks for primitives: ------------------------------------ 1996:-- End of Hugs standard prelude -----------------------------
こうして見ると、classesやtypeの宣言が大半を占めているな。これを制服出来れば haskellが自分の血肉になるんだな。
血圧アプリ Haskell版
-- bld data Field = Date | Hi | Low | Pulse deriving (Enum, Show) csvRead :: IO [[Int]] csvRead = do al <- readFile "current.csv" return $ map (\e -> read ("["++e++"]") :: [Int]) (lines al) getYM :: [[Int]] -> Int -> [[Int]] getYM cs n = filter (\x -> (x!!0 > b) && (x!!0 < e)) cs where b = n * 10000 e = n * 10000 + 3124 am, pm, ap :: Field -> [[Int]] -> [Int] am k cs = [x!!(fromEnum k) | x <- cs, x!!0 `mod` 100 < 12] pm k cs = [x!!(fromEnum k) | x <- cs, x!!0 `mod` 100 >= 12] ap k cs = [x!!(fromEnum k) | x <- cs] fi :: Int -> String fi v = replicate (8 - x) ' ' ++ show v where x = length $ show v ff :: Float -> String ff v = replicate (8 - x) ' ' ++ s where t = span (\x -> x /= '.') (show v) s = fst t ++ take 2 (snd t) x = length s nextYM :: Int -> Int nextYM n = if n `mod` 100 /= 12 then n + 1 else ((n `div` 100) + 1) * 100 + 1 ana :: [[Int]] -> Int -> IO () ana cs n = do if 0 == length ss then return () else do putStrLn $ (show n) ++ (stat $ am Hi ss) ++ (stat $ am Low ss) ++ " |" ++ (stat $ pm Hi ss) ++ (stat $ pm Low ss) ana cs (nextYM n) where ss = getYM cs n stat :: [Int] -> String stat cs = (fi $ maximum cs) ++ (ff $ (/) (fromIntegral (sum cs)) (fromIntegral (length cs))) main :: IO () main = do cs <- csvRead ana cs ((head $ head cs) `div` 10000)
将来の自分の為に、コードの説明。
mainは、CSVファイルを読んで、Intのリスト(1レコード相当)それが寄せ集まったリストを 返す。それをcsにバインド。次の行は、そのリストと、一番最初のレコードの年月を引数に して、関数anaを呼ぶ。anaが実質のmainになる。本来ならanaでやってる事をmainに展開 すべきだが、再帰を使って、次々に年月を進めて行く必要があるので、分けてる。
anaは、指定された年月のデータをgetYMで読み込む。読んだリストの長さがゼロなら、 もうやる事が無いので()と言うIOの無効値を返して終わり。有効なリストが得られたら、 そのデータを分解して、統計関数に渡す。統計関数からは結果が文字列で返ってくるので 、それを結合して表示。 そして、次の年月を目指して、自分自身を呼ぶ。
then句もelse句も返り値が IO()と 同じ型にしておかないとまずいので注意。何故なら ifは返り値を持つ、式だから。 昔は、これが理解出来なくて、haskellが嫌いだったんだけど、ようやく理解出来た。
statでは、データが並んだリストが渡ってくるので、それぞれに勝手な統計関数を適用して、 結果を文字列結合して返す。今の所、勝手な統計は、最大値と平均の2つを指定してある。 追加で最小値とか標準偏差ぐらいは入れてもよいかも知れない。
それぞれの統計値は8桁右揃えの文字列になるように調整してる。fiってのが整数用。ffって のが浮動小数点用だ。
ffっていうのを取り上げてみる。Floatを取って文字列にするんだぞ。までは分かるんだけど printf("%8.1f", v)相当って事は、本文の定義を見て悟ってねってのがHaskellの流儀。
主文がある。replicate n cで、cって文字をn個結合したものに文字列sを結合したのが 答え。細かい事は、where以下を読んでくれ。裁判の判決文だな。
t = span (\x -> x /= '.') (show v)
少数点の所で分割するのに、こんなコードを使っているけど、breakを使った方が分かりやすかったかな。
t = break (== '.') (show v)
また、少数点以下2桁目以降を切り捨ててるけど、やっぱり四捨五入して、少数点以下2桁表示 したいなんて事があるかも知れない。そんな時は、下記を参考に。
Hugs> break (=='.') $ show $ 2.0 + 0.005 ("2",".005") Hugs> break (=='.') $ show $ 2.016 + 0.005 ("2",".021")
主文に出てきたxとかsって変数名は、やけに短い。名前を考えるのを放棄してるな。 でも、一応習慣があるみたいだ。 リストの場合は最後にsを付ける。数値のリストなら ns、文字なら cs、任意のリストなら xsって具合。
実行結果
[sakae@fedora bld]$ ./bld 1107 132 120.8 85 77.6 | 127 112.2 78 70.1 1108 137 123.0 86 80.6 | 129 111.9 81 68.3 : 1512 131 127.0 75 74.0 | 122 121.5 72 70.5 ------ AM -------------------- -------- PM ------------------ yymm Hi.max Hi.avr Low.max Low.avr Hi.max Hi.avr Low.max Low.avr
下に注釈を入れておいた。元データは3212レコードだった。もっと昔のデータも 有ったんだけど、血圧計が壊れて買い換えたら、測定値に明らかな差が出たので 古いデータは破棄しちゃったんだ。
例によってスピード比べ。
[sakae@fedora bld]$ time ./bld > /dev/null real 0m0.187s user 0m0.066s sys 0m0.114s [sakae@fedora bld]$ time runghc bld.hs > /dev/null real 0m1.158s user 0m0.525s sys 0m0.630s [sakae@fedora bld]$ time runhugs bld.hs > /dev/null real 0m5.467s user 0m4.849s sys 0m0.560s
やっぱり、コンパイルしたやつは、速いわい。
read me
Learn You a Haskell for Great Good!
今年の更新はこれで終了です。来年も宜しくお願い致します。