OpenCVをMacでビルドしてHaskellから使う
OpenCVの32ビット版をHaskellからForeign Function Interface (FFI)で使いたかったのでやってみた。手動でインストールしたところ、MacPortsで入れるよりも多少手間はかかるが所要時間は遙かに短かかった(正味のビルド時間は15分もかからない。MacPortsだと一晩かかる)。忘れないうちに覚え書き。使用環境はMountain Lion on iMac Mid 2010。
手順は基本的にはここに書いてあるのに従えばよい。
http://opencv.willowgarage.com/wiki/InstallGuide
作業の流れ
- App StoreからXcodeをインストールし、そこからCommand Line Tools(gccやmakeが入っている)をインストールする。
- MacPortsからcmakeとpkgconfigの二つのパッケージを入れる。
- OpenCVのソースをダウンロードして展開し、cmakeでconfigureする。
- バイナリ作成用のフォルダ(今回は /opt/local/share/opencv-2.4.2 としておく)を作成してそこにカレントディレクトリを移動し、cmake <ソースのフォルダ>を実行する。
- 32ビットでビルドするため、CMakeCache.txtの書き換え、makeでビルド。
- cmakeが生成したCMakeCache.txtにCMAKE_C_FLAGS:STRING=という行とCMAKE_CXX_FLAGSという行があるので、それぞれ末尾に-m32を加える。
- make -j 4 とすると4スレッド並行でビルドしてくれる。iMac Mid 2010でものの10分もかからなかった。
- binディレクトリに必要に応じて手動でパスを通す。あるいはmake installする(上のガイドだと、共通のフォルダが散らかるので非推奨とのことだが、やってしまった)。
- pkg-config用のファイルであるopencv.pc(unix-installフォルダにある)を/usr/lib/pkgconfigの下にコピーし、chmod 644で読み取り可能に設定する。場合によっては(make installを使わなかったときなど)opencv.pcの内容を調整する必要があるかもしれない。
HaskellからC++を呼ぶときのビルドのスクリプトは以下のようになる。lib1.cpp, lib2.cppがcppでのソースコードで、main.hsがmainを含むHaskellソースファイル。
#!/bin/sh g++ -c lib1.cpp lib2.cpp -m32 `pkg-config --cflags opencv` ghc --make main.hs -lstdc++ lib1.o lib2.o `pkg-config --libs opencv`
GHCは32ビットコードしか出力できないとのことなので、OpenCVのライブラリ自体、そしてOpenCVとHaskellをつなぐC++のグルーコード(上のlib1.cpp、lib2.cpp)もHaskellからリンクするためには32ビットでビルドしておく必要がある。
できあがった実行ファイルは1.4MB。悪くない。
OpenCVをXcodeから使う方法については、ここにも記事があった。僕はXcode上でコーディング(リアルタイムでエラーが表示されるので便利)して、ビルドは上記のスクリプトで行っている。
Mac OSX Snow LeopardにOpenCVを入れてXCodeから使えるようにする手順
http://d.hatena.ne.jp/konisimple/20100811/1281526832
それと、OpenCVはC++で書かれているので、HaskellからFFIで呼ぶには、以下の記事にあるようにextern "C"を使うなどの工夫が必要。
[Haskell]FFI使ってC++バインディングに入門
http://mymo.blog8.fc2.com/blog-entry-52.html
これらを使って、HaskellでOpenCVのバインディングを書けることが分かった。その詳細はまた気が向いたときにでも記す。
今回の作業で初めてcmakeやpkg-configの存在を知ることができたのも良かった。
Haskellでスクレイピング - html-conduit/xml-conduitの使い方
Haskellでウェブサイトのスクレイピングをしたくていろいろ調べていた。 HaskellのHTML/XML パッケージは乱立気味*1であるが、WebフレームワークのYesodで採用されている以下のパッケージが良さそうだ。Conduitという効率的な新しいIOベースで(このところよく知らないが、http://tanakh.jp/posts/2012-07-01-conduit-0.5.htmlとかhttp://d.hatena.ne.jp/kazu-yamamoto/20120113/1326446266とかの解説が良さそう)、また探索用の関数も一通りそろっている。
- 読み込み
- xml-conduit
- html-conduit
- 書き出し
- blaze-html
(追記:CSSセレクタでスクレイピングの出来るdom-selectorライブラリを作った。http://hackage.haskell.org/package/dom-selector cabal でインストールできる。)
今回は読み込みにフォーカスする。HTMLをファイルから読み込んで出力するために最低限必要なコードは以下のようになる。
{-# LANGUAGE OverloadedStrings #-} import Text.XML.Cursor import qualified Text.HTML.DOM as H (readFile) import qualified Data.Text as T (Text) import qualified Data.Text.Lazy as TL (Text,concat) import qualified Data.Text.Lazy.IO as TI (writeFile) import Text.Blaze.Html (toHtml) import Text.Blaze.Html.Renderer.Text (renderHtml) test :: Int -> IO () test n = do doc <- H.readFile "input.html" let c = fromDocument doc -- fromDocumentはText.XML.Cursorで定義された関数 let cs = f n c putStrLn $ (show n) ++ ": " ++ (show (length cs)) ++ " elements" TI.writeFile ("output."++show n++".html") (render cs) -- Axisの定義 f :: Int -> Cursor -> [Cursor] -- f :: Int -> Axisとも書ける f 1 c = c $// element "div" -- CSSセレクタ 'div' に相当 f 2 c = c $// element "div" &/ element "li" -- 'div > li' f 3 c = c $// attributeIs "class" "novel" &// attributeIs "id" "natsume" >=> followingSibling >=> anyElement -- '.novel #natsume ~ *' f 4 c = c $// attributeIs "class" "list" >=> attributeIs "id" "language" -- ".list#language" f 5 c = c $// element "div" &// element "li" render :: [Cursor] -> TL.Text render cs = TL.concat $ map (renderHtml . toHtml . node) cs
以下のようなHTMLをinput.htmlとして用意。
<!DOCTYPE HTML> <html lang="ja"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div class='info'> <div class='list' id='language'> <ul id='morning'> <li class='German'>Guten Morgen.</li> <li class='Japan'>おはようございます</li> <li class='USA'>Good morning.</li> </ul> <ul id='day' class='highlight'> <li class='German'>Guten Tag.</li> <li class='Japan'>こんにちは</li> <li class='USA'>Good afternoon.</li> </ul> <ul id='evening'> <li class='German'>Guten Abend.</li> <li class='Japan'>こんばんは</li> <li class='USA'>Good evening.</li> </ul> </div> <div class='novel'> <ul id='novel'> <li id='natsume'>夏目漱石</li> <li>森鴎外</li> <li>志賀直哉</li> </ul> </div> </div> </body> </html>
実行すると、
*Main> mapM_ test [1..5] 1: 3 elements 2: 0 elements 3: 2 elements 4: 1 elements 5: 24 elements
おおむねいい感じ。*2
output.3.html
<li>森鴎外</li><li>志賀直哉</li>
output.4.html
<div class="list" id="language"><ul id="morning"><li class="German">Guten Morgen.</li> <li class="Japan">おはようございます</li> <li class="USA">Good morning.</li> </ul> <ul class="highlight" id="day"><li class="German">Guten Tag.</li> <li class="Japan">こんにちは</li> <li class="USA">Good afternoon.</li> </ul> <ul id="evening"><li class="German">Guten Abend.</li> <li class="Japan">こんばんは</li> <li class="USA">Good evening.</li> </ul> </div>
文字列の型
Haskellでは文字列型はString, ByteString, Textの大きく分けて3つがある。
- Stringはtype String = [Char]という定義なのでhead, tail, concatなどリスト処理関数がすべて使える。ただし大きい文字列を扱うと遅い。
パフォーマンス改善のために作られたのが以下の二つ。
- ByteStringはその名の通り、バイナリなどにも使い、まさにバイト列。
- TextはUnicode文字列を表現するためのもの。UTF-16の内部表現を持つ。
さらに、ByteStringとTextはそれぞれstrictとlazyの2バージョンがあり、ライブラリによってどれを取るか異なるので、異種間を繋ぐには変換が必要。(このあたりコンパイル時に型が合わないことで分かるので、慣れればただ面倒なだけなのだが、初心者には理解までの障壁高い。)同種の操作は同名の関数で定義されていることが多いので、違うパッケージからimport XXX as Yとして区別しなければならない状況が頻繁に発生する。*3
今回は出力で、Data.Text.Lazyモジュールで定義されているlazyなTextを使っている。
要素の探索(traversing)
探索にはText.XML.Cursorモジュールを使う。型がけっこう難しく、最初なかなかコンパイルが通らなかった。
Cursor型とAxis型が重要。CursorはDOMツリーとその中のノードへのポインタのセット*4。AxisはCursorを取ってCursorのリストを返す関数の型。目的の要素のCursorを得た後、欲しい値(子要素数、innerText、attribute、など)を得るという流れ。
type Cursor = Text.XML.Cursor.Generic.Cursor Text.XML.Node type Axis = Cursor -> [Cursor]
Text.XML.Cursorモジュール内に様々なAxisが定義済みで、それを&//, >=>などの演算子(コンビネータ)で繋いで探索処理をするのが基本。
よく使うAxis
- element :: Name -> Axis
- attributeIs :: Name -> Text -> Axis *5
- checkElement :: Boolean b => (Element -> b) -> Axis *6
など。
>=>はAxisとAxisをつなぐ役割。>=>は一般にはモナドのKleisli compositionというものだそうで、Control.Monadで定義されている。圏論はよく知らなくても、型を見ると意味が分かる(実装よりも型を見た方が意味が分かりやすい。これぞ型の威力。)
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c) f >=> g = \x -> f x >>= g
ここではAxis(= Cursor -> [Cursor])を>=>でつなぐので、mは[]に相当する、よって以下の意味。
(>=>) :: (a -> [b]) -> (b -> [c]) -> (a -> [c]) (f >=> g) x = concatMap g (f x) -- リストにおいては x >>= f = concatMap f x
もしAxisに対して単に他のAxisを順次適用していくと、リストの入れ子が深くなってしまうところ、>=>を使うことでAxisの結果の入れ子をflattenする効果がある。なので、
g :: Axis g c = c $// element "div" >=> attributeIs "id" "language" >=> attributeIs "class" "list" -- CSSセレクタ 'div#language.list' に相当
と好きなだけAxisをつなげていくことができ、最終的な型は[Cursor]となる。
&/, &//, &.// はそれぞれ子要素(children)、子孫要素(descendants)、自分自身+子孫要素に対して右辺のAxisを適用するコンビネータ。(これらはみな右結合。)
(&/),(&//),(&.//) :: Axis node -> (Cursor node -> [a]) -> (Cursor node -> [a]) f &/ g = f >=> child >=> g f &// g = f >=> descendant >=> g f &.// g = f >=> orSelf descendant >=> g
左辺の結果にそのまま右辺を適用するのが>=>であり、これら3つはその拡張といえる(aをCursor nodeと置き換えれば&/, &//, &.//の型は>=>と一致する)。右辺の関数の返り値は[Cursor]でなくても良い。例えば、
f2 :: Cursor -> [T.Text] f2 c = c $// element "ul" >=> attributeIs "id" "evening" &.// content
とすることで、要素ごとのinnerTextのリストが取得できる。
一方で、$/, $//, $.//は左辺にAxisでなく、一つのCursorをとる。(これらもみな右結合。)よって、
c $// element "div" &/ element "li" -- CSSセレクタ 'div > li'に相当。
は正しいが
c $// element "div" $// element "li" -- element "div" は Cursor -> [Cursor] であり、$//の左辺であるべきCursorに適合しない。
は型エラーとなる。
これらのコンビネータを使うことで、リストを返す関数の逐次適用の表記が簡潔になるのだが、基本はCursorあるいは[Cursor]に対してAxis(=Cursorをとって[Cursor]を返す関数)を順次適用するというだけの話である。よくわからなくなったらmapやconcat, concatMapに立ち返ればよい。
*1:しかも別のパッケージが同じモジュール名前空間(Text.XMLなど)を使うので新参者を混乱させる
*2:test 5だけが結果がおかしい。input.htmlでdivがネストしているため、その子孫要素の li をダブってカウントしてしまう。
*3:このあたりこれらに共通の型クラスが用意されれば楽になるのか? IsStringみたいなノリで多相の相互変換が自動で出来たりすればいいのかなと。
*4:元々CursorはText.XML.Cursor.Genericモジュール内に任意のノード型を取る形式で定義されていて、それをText.XML.Cursorモジュールがtype Cursor = Text.XML.Cursor.Generic.Cursor Text.XML.Nodeとして用いることでCursorの定義を上書きしている。ややこしいが、この上書きされたCursorがAxisの引数。
*5:NameはIsStringのインスタンスで、OverloadedStringsプラグマを有効にすれば文字列リテラルから自動的に変換される
*6:BooleanはBool, List, Maybe, Eitherがインスタンスであり、空でないリストはTrue/空リストはFalse、Just aはTrue/NothingはFalse、といった具合に対応する。
HaskellでのHTMLパージング with XmlHtml
そして同じことがXmlHtmlライブラリで1時間もせずに達成できた。
http://hackage.haskell.org/package/xmlhtml-0.1.3
XmlHtmlライブラリは、
$ cabal install xmlhtml
でインストールできる。
{-# LANGUAGE OverloadedStrings #-} import qualified Data.ByteString.UTF8 as B import qualified Data.ByteString.Lazy as BL import qualified Data.Tree as T import qualified Data.Text as Tx import Data.List import Text.XmlHtml main = do str <- getContents case parseHTML "stdin" (B.fromString str) of Left err -> putStrLn err Right x -> putStrLn $ test x test :: Document -> String test d = concatMap (T.drawTree . toTree) $ docContent d toTree :: Node -> T.Tree String toTree n = T.Node (showNode n) (cs n) where cs (Element _ _ xs) = map toTree xs cs _ = [] showNode :: Node -> String showNode (TextNode t) = "\""++Tx.unpack t++"\"" showNode (Comment t) = "<-- "++Tx.unpack t++" -->" showNode (Element t as _) = Tx.unpack t ++ ": "++s as where s [] = "[]" s as = "["++intercalate "," (map s' as)++"]" s' (k,v) = Tx.unpack k ++ "=\""++Tx.unpack v++"\""
追記:このXmlHtmlモジュール、parseHTMLがByteStringを入力に取るためなのか、日本語を含むHTMLはパーズに失敗するようだ。
さらに追記:Data.ByteString.UTF8をインポートしてその中のfromStringをStringにかけてやることで、UTF-8対応のByteStringを作ることができた。http://stackoverflow.com/a/2089195
Haskell+ParsecでHTMLのパーシング
Haskellの紹介で、「パーサーコンビネータを使うとパーシングが簡単にできる」と良く聞くので、僕もParsecを使ってちょっと試してみた。
情報源
Parsecの使い方は本家に置いてあるpdfが古いが短くて割と分かりやすい。というか他にあまり資料が見つからない。
http://legacy.cs.uu.nl/daan/download/parsec/parsec.pdf
あとはライブラリの最新ドキュメントを眺めればどんなコンビネータを使えるのかが分かる。
http://hackage.haskell.org/package/parsec-3.1.2
HTMLのパージングに関しては、W3Cのページが参考になる。ただ今回は仕様に忠実に実装したわけではない。
http://www.w3.org/TR/html5/parsing.html#parsing
プログラム
試行過程をこの記事に書こうかと思ったがそうとう面倒なのでとりあえずはソースのみ置くことにする。Gistにソースを置いた。
https://gist.github.com/2904427
HTMLをパースしてツリー構造を作る。パーサ本体は150行くらいになった。さらに、ツリー構造を表示/HTMLを生成するコードや、QuickCheckのためのarbitrary関数の定義などを含めて計300行。QuickCheckでは、
- 任意のツリーを生成
- それをHTMLに書き出し→再びパースして生成したツリー
この2つのツリーが等価であるべしというprop_html関数を定義した。
使ってみる
main関数は標準入力のHTMLをパースしてツリーを出力する。http://www.ne.jp/asahi/hishidama/home/tech/scala/index.htmlから持ってきたHTMLをパースしてみる。
$ runhaskell htmlparse.hs < test.html html: [] | +- head: [] | | | +- " " | | | +- meta: [http-equiv="Content-Type",content="text/html;charset=Shift_JIS"] | | | +- " " .....(略)..... | | | | | +- "さんの" | | | | | | | | | | | `- a: [class="ext",target="class-diagrams.appspot.com",href="http://class-diagrams.appspot.com/"] | | | | | | | | | | | `- "scalaとかjavaとかのclass図を表示するサイト" | | | | | | | | | +- " " | | | | | | | | | +- li: [] | | | | | | | | | | | +- "kmizuさんの" | | | | | | | | | | | `- a: [class="ext",target="sites.google.com",href="https://sites.google.com/site/scalajp/"] | | | | | | | | | | | `- "プログラミング言語Scala 日本語情報サイト" | | | | | | | | | `- " " .....(略).....
わりと良い感じに動いている! 結構時間かかったしここまで作るのにあまり簡単でもなかったけど。特に、try関数の使い方がわりと難しかった。choice あるいは <|>を使う状況で、選択される各々の中にtryを書き忘れると間違ったパーサが選ばれてしまいエラーになったりした。どこまでtryを書けば良いのか、という判断をする際に少し混乱した。
現時点で分かっている制限
- 入力ファイルの文字コードがUTF-8しか受け付けないっぽい。エンコードの件は未調査。
- タグは網羅していない。ただ冒頭のdata宣言にタグを足せば良いだけ。
- invalidなHTMLや、g:plusoneなどというGoogle+独自のタグなどはエラーになる。
Haskellで単位付きの計算
Haskellの練習で、科学計算で使うような単位付きの計算を作ってみた。UnitNum型はNum, Show, Eqクラスのインスタンスで、==, +, -, *, show などが使える。
Gistはhttps://gist.github.com/2841750
-- Haskell unit calc test data (Num a) => Unit a = Unit a a a -- dimensions of meter, kilogram, and second deriving (Eq) instance (Num a)=>Show (Unit a) where show (Unit m k s) = (f "kg" k) ++ " " ++ (f "m" m) ++ " " ++(f "s" s) where f s 0 = "" f s 1 = s f s d = s ++ "^" ++ show d instance (Num a,Num b)=>Show (UnitNum a b) where show (UnitNum a u) = show a ++ "[" ++ show u ++ "]" data (Num a, Num b) => UnitNum a b = UnitNum a (Unit b) deriving (Eq) instance (Num a,Num b) => Num (UnitNum a b) where (UnitNum a u1) + (UnitNum b u2) | u1 == u2 = UnitNum (a + b) u1 | otherwise = error "Dimensions don't match." (UnitNum a u1) - (UnitNum b u2) | u1 == u2 = UnitNum (a - b) u1 | otherwise = error "Dimensions don't match." (UnitNum a u1) * (UnitNum b u2) = UnitNum (a * b) (unitMult u1 u2) signum (UnitNum a u) = UnitNum (signum a) (Unit 0 0 0) abs (UnitNum a u) = UnitNum (abs a) u fromInteger x = UnitNum (fromIntegral x) (Unit 0 0 0) unitNum :: (Num a,Num b)=> a -> (b,b,b) -> UnitNum a b unitNum v (m,k,s) = UnitNum v (Unit m k s) unitMult :: Num a => Unit a -> Unit a -> Unit a unitMult (Unit m1 k1 s1) (Unit m2 k2 s2) = Unit (m1+m2) (k1+k2) (s1+s2) unitDiv :: Num a => Unit a -> Unit a -> Unit a unitDiv (Unit m1 k1 s1) (Unit m2 k2 s2) = Unit (m1-m2) (k1-k2) (s1-s2)
やたらtype constraintsをつける場所が多くなった。まだこのあたりあまりよく分かっておらず、何回もコンパイルエラーで=>での指定が足りないと言われ、しばらく修正してやっとコンパイルが通った。
使ってみる。
Prelude> :l unitcalc.hs [1 of 1] Compiling Main ( unitcalc.hs, interpreted ) Ok, modules loaded: Main. *Main> let a = unitNum 1 (0,1,0) *Main> let b = unitNum 2 (1,0,-2) *Main> a 1[kg ] *Main> b 2[ m s^-2] *Main> a * b 2[kg m s^-2] *Main> a + b *** Exception: Dimensions don't match.
良い感じだ。
割り算(/)を使うにはFractionalクラスのインスタンスにしないといけないが、Fractionalの使い方(というか、数値クラスの構造)をまだよく分かっていないので未実装。
それと、本当は次元が違う量の加減演算をコンパイル時にチェックしてはじいたり出来たらクールなんだろうけど。数学の行列の演算で次元をコンパイル時にチェックしようというのと同じような話か。そんな話は聞いたことがないので、おそらく不可能だろう。たかだか数次元までの整数次元だけ対応するなら異なる次元ごとに違う型を用意すれば良いのかもしれないけど、それは現実的な解とは言いがたい。(よく知らないが、でもそういう研究はどこかでされていそう。)
Haskellの学習のコツ?
まだHaskell初心者でLearn You a Haskell for Great Goodをちょくちょく読んだりしている。実際にアプリなどを作るまでは至っていないのだが、少しずつ全体的な感覚はつかめてきた。
関数の定義の仕方、パターンマッチの意味と使い方を理解する
昔初めてHaskellにトライしたときはここで早速つまずいた。関数定義において、関数の引数にa,bといった一般的な引数(小文字で始まる)あるいはデータコンストラクタ(大文字で始まる)を置くことができる。関数定義の左辺のデータコンストラクタは括弧でくくる。リストでのパターンマッチというのは「:」が記号のため初心者は少し混乱するかも。Treeとかの例のほうがわかりやすい。一回分かれば些細な違いなのだが。
data定義の意味
data Hoge a = Hoge a
=の左辺は型コンストラクタ、右辺はデータコンストラクタ。データコンストラクタはパターンマッチに使うことができる以外は、一般の関数と同じように(カリー化など)使え、名前に記号を使うことも出来る。データコンストラクタの意味は、パターンマッチで使うときになって初めて明確になる。昔学んだときは、データコンストラクタの実体は何?とか、データコンストラクタを呼ぶと何が起きるの?みたいな事を考えて混乱してしまった記憶がある。
再帰やfoldlなどを使った関数型特有の計算方法に慣れ親しむ。
リストは先頭要素と残りのリストを引数に持つデータコンストラクタ「:」で出来ていて、パターンマッチを使って再帰的にたどれる。ライブラリの中のData.Listモジュールにリスト上で計算する一連の関数がある。階乗の再帰など、ループを再帰に変換する際は、ループのカウンタがなくなる分、それが再帰関数の引数として追加される。foldやscan系の関数はEuler projectの解答集などを見ると、初心者にはなかなか思いつかない便利な使い方ができるようである。
IOを使うのにモナドの理解は必要ない。ひとまず仕組み自体はブラックボックスに入れておいて、<-とreturnの型と使い方を理解する。
モナドは計算をカプセル化する仕組み。カプセル化するため、中に文脈を持たせられる。IOモナドは特別なモナドで、カプセル化した中身の取り出しに制限がある(IOモナド内でのみ取り出し可能)。また、IOモナド内の関数は処理系によって逐次実行される。
型クラスはJavaでいうインターフェイス
型クラスは、型に対してある一連の関数が使えることを示すための仕組み。ただし型シグネチャのみでなく実装も含められる。それと、既存の型に対して後から自分の望む型クラスを追加可能。オブジェクト指向と違い、関数が型に所属するわけではないので、ある型に対して適用可能な新しい関数を型定義とは別にinstance宣言内で定義できる。上手く表現できないが、とりあえず型クラスは非常にパワフルな機能だとのことである。
- 型クラスについてのSimon Peyton Jonesによる言及(pdfの58〜62ページ)
- http://conferences.oreillynet.com/presentations/os2007/os_peytonjones.pdf
型クラスのインスタンスを定義するとき、たとえばEqクラスの定義は
class Eq a where (==) :: a -> a -> Bool ---以下略
なので、aには具象型が必要。
つまり、
instance (Eq m) => Eq (Maybe m) where
などと定義する。
一方でFunctorクラスの定義は
class Functor f where fmap :: (a -> b) -> f a -> f b
なのでfは引数が一つの型コンストラクタでなければならない。
つまり、
instance Functor Maybe where
や
instance Functor (Either a) where
などと定義する。
型クラスを理解した上で、Functor → Monadと学んでいく。
Monadは型クラスとして定義されている。そしてそのインスタンスは、Monadクラスの関数が3つのモナド則を満たすべく定義される。
Haskellにおいてモナドばかりが難しいと言われるが、モナドに関しては以下のレベルの理解でさしあたりは不便はないのでは、と思う。
- Haskell Day2012 - 参照透過性とは何だったのか(34〜51ページ)←イメージをつかみやすい
- http://www.slideshare.net/RuiccRail/haskell-day2012/34
- モナドはメタファーではない(Scalaを使った説明)
- http://eed3si9n.com/ja/monads-are-not-metaphors
まとめ
モナド自体よりは、今まで手続き型で書いていたアルゴリズムをどうやって関数型でスマートに書くかというイディオムを学ぶのに結構労力がいるのでは、という感じがしている。それと、まずはデータコンストラクタとパターンマッチの感覚を理解するのが、案外多くの人にとって最初の山なのかなーと思った。
Haskell for Great Goodで使われている英語は平易なので、オンラインで無料の原文を読むのをおすすめする。後でまた同じところではまると嫌なので、自分の理解している範囲で覚え書きとして記してみた。現時点ではポイントを外している可能性もあることを言い訳として付け加えておく。修正があればその都度更新予定。
MeteorでWebアプリを書いてみて気づいたポイント
Othello by Meteor: Webアプリ初心者が5時間弱かけて書いたもの
https://github.com/nebuta/MeteorOthello
公式ドキュメントhttp://docs.meteor.comは英語だが短いので全部読み通すのもさほど苦ではないだろう。ドキュメントを読みながら、meteor create --example todosで作られるToDoアプリを読みながら改変していくのがわかりやすくて良さそう。
clientとserverの二つのフォルダに、それぞれのコードを置き、共通のコードはそれ以外の部分に置けばよい。Meteor.is_server()を使えばコード内で環境を判別可能。
JSに書く大まかな処理の流れ
- Collection(データベース)をサーバーとクライアントにそれぞれnew Meteor.Collection("同じ名前")で作る。
- サーバー側のコードで、クライアントに対してデータベースをMeteor.publish()でパブリッシュする。
- クライアント側のコードで、サーバーのデータベースをMeteor.subscribe()で購読する。
- セッションの状態は、Session.set(), Session.get()で管理する。
- このSessionの意味は、まだイマイチ理解していない。
- 初期化コードはMeteor.startup()の中に書く。
- ただし、Collectionの作成やsubscribe, publishはstartup()の中ではなく、トップレベルの地の文で行う。このあたりの違いがちょっとよく分からない。
HTMLに書く大まかな処理の流れ
.htmlの拡張子がついているが、正式なHTMLではない。ルート要素のはなく、
ととがルートに並ぶ。{{ }}の中身がテンプレートとして置換される。Handlebars(http://handlebarsjs.com/)というテンプレートエンジンを使っているそうだ。あまりパワフルではなく、適当に選んだっぽい感じがする。入れてほしいエンジンを教えてくれとかMeteorの公式docにも書いてあるし。othello.html
<head> <title>Othello</title> </head> <body> {{> board}} {{> tools}} </body> <template name="board"> <div id="board"> {{#each cells}} {{> cell_item}} {{/each}} </div> </template> <template name='cell_item'> <div class='cell {{piece}}' style='{{style}}'> <span class="piece"> </span> </div> </template> ....(省略)
{{> board}}という記述は、nameがboardであるtemplateで置換される。テンプレートは入れ子にでき、最終的にJavaScriptの関数が呼ばれ、そこで返された文字列がHTMLとして代入される。
たとえばcell_itemテンプレートの中の{{piece}}は、Template.cell_item.pieceというJavaScriptの変数に代入された関数が呼ばれる。
Template.cell_item.piece = function(){ return {'w':'white','b':'black','n':'none'}[this.piece]; }
ここでのthisは何か。cell_itemテンプレートは親テンプレート"board"の中の{{#each cells}}ブロック内で呼ばれている。cellsはTemplate.board.cellsでCells.find()が返すイテレータ(Meteor.Collection.Cursor)であり、thisはそのイテレータ各要素。
Template.board.cells = function(){ return Cells.find(); }
CSS
普通に書くのみ。
データベースの使い方
基本的には標準的なMongoDBのようだが、初めて使ったので覚え書きのためにいくつか例を書いておく。
新規作成して要素追加
Cells = new Meteor.Collection('cells'); Cells.insert({board_id: 'b1',x:x,y:y,piece:'n'});
検索して該当要素の変更をモニター
if(stopHandle) stopHandle.stop(); stopHandle = Boards.find({black: true, white: true}).observe({ added: function(){ Boards.update({},{$set: {ready: true}}); } });
updateメソッドは$setの中にハッシュを入れ子にすることで、他のキーを変更せずにpieceだけを変更できる。そうでないと当該要素の他のキーを消してしまう。
レコードの全削除
Cells.remove({});
まとめ
Meteorに関して以上のことを理解すれば後は速い。自分の場合、テンプレート周りとデータベース周りで若干ハマったような記憶がある。それとHTMLで盤面を書く書き方。最終的に@tkihira氏(http://twitter.com/tkihira)の「1時間でオセロを作る動画」http://www.nicovideo.jp/watch/sm8391299のやり方で、position: absoluteに設定して座標を設定する方法にした。それと、石が置けるかどうかの判定も@tkihira氏のアルゴリズムを使った。ありがとうございます。もしかしてと思いながら、その動画を流しながら書いていたのだが、1時間ではさすがに出来ず、4回再生+αでどうにか終わった。やっぱりHTMLのレイアウトの勉強がもっと必要だな。どこかに体系的に学べるリソースがないだろうか...。Webアプリの経験もRoRの経験もほとんどないので既存の技術との比較はあまりできないが、リアルタイムWebアプリが簡単に作れるとなると、こんな事が出来たら、みたいな漠然としたアイデアはいろいろ湧いてくるな。