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)

Emacs for Haskell

上記で、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!

maoeのブログ

Book H

Haskell 脳の恐怖

任意精度算術ライブラリGMP

GMPの仕組み

コンピューター時代の大局感

Effective Python読んだ積もり

今年の更新はこれで終了です。来年も宜しくお願い致します。