Haddockでお手軽ドキュメント作成(仮)
静的型付けの関数型言語で「設計」と言えば、「型設計」の事になりまして、変に頑張ってOfficeツール*1で文章書いたりとかするより、スタブコードべろべろ書いてコンパイルが通った時点でそれを元にドキュメントを書き起こしたほうが圧倒的に正確だし効率が良いんじゃないかと、思うわけです。
で、JavaDocとかXMLDocumentみたいに、ソースコードのコメントから資料を生成する事ができるツールも割と一般的だったりするのですが、我らがHaskellも例に漏れず、Haddockというツールでドキュメントの自動生成ができたりします。
というわけで、簡単なHaddockの使い方を、ざざざっと纏めてしまいましょー。
インストール方法の話は環境によってまちまちと思われるので割愛。*2
まず、てけとーな場所に、以下の3ファイルを用意します。
Main.hs
-- |module of main module Main where import Data.MyFunctor import Data.MyList -- |main program uses MyFunctor and MyList main :: IO () main = print $ mymap (*2) (MyList 5 (MyList 10 (MyList 15 Nil)))
Data/MyFunctor.hs
-- | module for reinventing functor module Data.MyFunctor(MyFunctor(..)) where -- | reinventing functor class MyFunctor f where -- | reinventing fmap mymap :: (a -> b) -> f a -> f b
Data/MyList.hs
-- | module for reinventing list module Data.MyList(MyList(..)) where import Data.MyFunctor -- | reinventing list data MyList a = MyList a (MyList a) | Nil deriving (Show, Read, Eq) -- | MyList is instance of MyFunctor instance MyFunctor MyList where mymap _ Nil = Nil mymap f (MyList x xs) = MyList (f x) (mymap f xs)
runghcでちゃんと動く事を確認したら、haddockコマンドを入力...
$ mkdir Docks $ haddock -h Main.hs -o Docks haddock coverage for ./Data/MyFunctor.hs: 2/2 100% haddock coverage for ./Data/MyList.hs: 2/2 100% haddock coverage for Main.hs: 2/2 100% Warning: Main: could not find link destinations for: GHC.Types.IO
すると、Docks内に次の画像のように、詳細なHTMLドキュメントが作成され・・・
・・・(´・ω・`)おや?MyListがMyFunctor型クラスのインスタンスだとゆー情報が何処にも無いですね・・・
Preludeや、外部モジュール等の関数が全てWarningとして列挙されてしまっていますし、要確認。
あと、バージョンにもよるのかもしれませんが、ブログ主の環境では日本語が使えないみたいです。
まぁ、最悪日本語が使えないのは、ローマ字頑張ってナンチャッテ英語書けば良さそうですが、型クラスのインスタンス情報が表示されないのはちょっと致命的ですね・・・
Hackageとか見てみるとちゃんと出力されてるみたいなので、もうちょっと調べてみることにします。
ヽ(〃l _ l)ノ
http://c4se.hatenablog.com/entry/2012/12/01/180025
monga = mod mOm0n ngA momoNga = momo ngA momoNga : mOm0n ngA (mom0 ngA momoNga) mom0 = drop m0m _ [] = [] m0m on ((ga, mon):m0) = (momo ga (mom0 mon on)) ++ m0m on m0 mom = [4,5,3,4,5,5,4,4,4,4,4,4,3,4,5,5,4,4,4] momon ga = let mOMONGA = 11035153 in ga : (momon $ mO monga 32768 ((+) ((*ga) mOMONGA) 12345)) onga = [1768,17404,433,21215,24661, 1960,4195,9417,6259,1961,14362,22,19,433,18667,26417,7924,14631,25914] momomonga = cycle mo = momo 30000. monmomo (let omonga = (`monga`13) in (.) ngaga omonga.(`m0MO`1000)) $ momon 65536 where m0MO = div mom0nga = zipWith main = mOmo (nGa.momo 40) $ momo 6 (mOm0n 41 m0m0n) where mOmo = mapM_ momO = concat momomomomonga = replicate monmomo = map ngaga = succ m0m0n = momO $ mom0nga (let momonGa = momomomomonga in momonGa) (m0m mo momonga) (momomonga " *") where momonga = momomo mom onga mO = flip momo = take nGa = putStrLn momomo = zip
A HASKELL ADVENTURE IN WINDOWS(超訳)
Haskellでゲームを作るのは良いんだけど、Windowsでも動作するようにはしたいよなぁとは思っているので、その方法についてあちこち調べ回っていて、hackageのSDLのページからリンクをたどってたどり着いたANIMAL-MACHINE.COMというサイトのA HASKELL ADVENTURE IN WINDOWSという記事が解りやすかったので、和訳してみました。
キャプチャ画像とかは貼ってませんし、もとより翻訳とかマトモにやった事無い上に英語力がアレな感じなので、所々直訳過ぎて意味が伝わらないとか、意訳しすぎて元の文が死んでたりとか、ってゆーか意味が間違ってたりとかあるかもしれないので、英語読める人は原文へGOです。
多分、間違えまくりなので、得意な人はバシバシ突っ込んでください。
恥ずかしいので直します。
あ、あと、ここに書かれている事全部試したわけでは無いので、活用は自己責任でお願いします。
以前、私のブログにHaskellを使った最近のプロジェクトーSDLを用いた戦略ゲームーについて投稿した事がある。
私は主要なOSとしてGNU/LinuxのUbuntu10.4を使っているが、Windowsでも私のプロジェクトが実行できる事を確認したい。それは何故かというと、私の妻が使うからだ。
これを理解するのは、当初私が想定していたものよりとても難しいものになった、一般的に考えて、LinuxでHaskellでコーディングするほうが簡単だと思われる。
VirtualBox 3.1.6 OSE上のWindows7 x64で私の使った新しいインストール方法*1をこの記事で紹介しよう。
あなたが使っているWindowsのバージョンによっては、この記事の有益度は変わってくるかもしれない。
Haskellのインストール方法、問題となるFFI bindingsのインストール方法、同様にgit toolsのインストールからソースコードリポジトリのクローン、ビルドの方法・・・この記事で全てまかなう予定だ。
大部分は私自身へのノートだが、おそらく他の誰かがここを見つけて役立てるだろう。
最初に考えるのは、基本的なフリーソフトウェアーー私が見つけたとても便利なーーをインストールする事だ。素敵なFirefoxウェブブラウザ、イカしたプログラミング用テキストエディタNotead++、最適なコマンドライン環境Console2、良い感じのアーカイブユーティリティは7-zipと呼ばれている。
これらのパッケージは後に欲しい機能を全て提供しているが、もしあなたがそための他のソフトウェアを活用しているなら、これらはまったく要求しない。
Haskell、SDL、SDL-image、SDL -ttf FFI bindingsのインストール
Haskell Platform for Windowsのインストールは簡単だ。あなたはここから最新のバージョンをダウンロードできる。私が使っているのはバージョン2010.1.0.0だ。
インストールはデフォルトのまま承認していけばしていけば良い。これによってGHCコンパイラとGHCiインタプリタがインストールされ、環境変数にPATHが追加される。
プラットフォームは、よく使うパッケージだけをインストールし、そしてSDLや関連するライブラリはカットされてしまっている。Windows上でのライブラリのインストールは、手動で編集しビルドファイルを作成する事で完了させなくてはいけない。
MinGW32向けSDL開発パッケージSDL-devel-1.2.14-mingw32.tar.gz、SDL_image開発パッケージ SDL-devel-1.2.14-mingw32.tar.gz、SDL_ttf開発パッケージSDL_ttf-devel-2.0.9-VC8.zip・・・をダウンロード。これら全てを、以下のスクリーンショットのようにC:\フォルダへ解凍しよう。
※原文ではここにC:\直下にSDL_image-1.2.10、SDL_1.2.10、SDL_ttf-2.0.9というフォルダが配置されたスクリーンショット
これらはHaskellのFFI bindingから使いたいC言語のヘッダーとライブラリ提供する。
私たちがインストールする最初のbindingはSDLになる。hackage上のSDLのページへ移動し、下のほうのSDL-0.5.9.tar.gzアーカイブ*2をダウンロードする。このソフトウェアをあなたにとって手頃な場所ーーその具体的な場所は重要ではないーーに解凍しよう。
SDL-0.5.9フォルダ内のWIN32ファイルにWindows上のHaskellビルドの手順が書かれている。以下はその要約版だ。
1.お好みのエディタでSDL-0.5.9/SDL.cabalを開く
2."Extra-Libraries: SDL"と書かれている行をExtra-Libraries: SDL.dll SDLmain”に変更する
3.変更した行の下に、以下の二行を追加する(元のバージョンから正確に)
Include-Dirs: C:\SDL-1.2.14\include\SDL Extra-Lib-Dirs: C:\SDL-1.2.14\lib
4.cmd.exeまたはConsole2どちらか*3のコマンドラインウィンドウを開く。私はこれを管理者として実行する、何故なら、このコマンドラインを使って最終的にデフォルトのC:\Program Files (x64)へ、Haskellパッケージをインストールするからだ。
5.以下のコマンドを実行する
runghc Setup.lhs configure
configureスクリプトに関するエラーメッセージが表示されるが、無視する。
※原文ではここに警告文が表示されているスクリーンショット
6.次の2つのコマンドを実行する。最後の一つは権限を要求し、C:\Program Files\Haskellフォルダに書き込みを行う。
runghc Setup.lhs build runghc Setup.lhs install
※原文ではここに警告文が表示されているスクリーンショット
主となるSDLのビルドが終わったら、SDL-image bindingのビルドを行う。多くのステップは私たちが丁度SDLのために行ったのと同様になる。
hackage上のSDL-imageのページへ移動し、SDL-image-0.5.2.tar.gzファイルをダウンロードし、何処か手頃な場所ーー私はデスクトップに配置しているーーに解凍する。
1.お好みのエディタでSDL-image-0.5.2\SDL-image.cabalを開き、ファイルの最後に以下の行を追加する。
Include-Dirs: C:\SDL_image-1.2.10\include
2.SDL-image-0.5.2\Graphics\UI\SDL\Image\Version.hscファイルを開き、#include “SDL_image.h”の下に追加。
#include "SDL.h" #ifdef main #undef main #endif
この処置はここで見つけた。
3.以下のコマンドを実行し、configureスクリプトに関して文句言ってくるのは無視する。
runghc Setup.lhs configure runghc Setup.lhs build runghc Setup.lhs install
これも同じようにインストールできる事の証明がこちら。*4
※ここに上記3つのコマンドを実行した画面のスクリーンショット
最後の必要なbindingはSDL_ttfだ。手順は既にあなたの見慣れたものになっているはず。hackageのSDL-ttfのページからSDL-ttf-0.5.5.tar.gzをダウンロードし、何処かにこれを解凍しよう。
1.お好みのエディタでSDL-ttf-0.5.5\SDL-ttf.cabalを開き、ファイルの最後に以下の行を追加。
Include-Dirs: C:\SDL_ttf-2.0.9\include
2.SDL-ttf-0.5.5\Graphics\UI\SDL\TTF\Version.hscを開いて#include “SDL_ttf.h”の下に追加。
#include "SDL.h" #ifdef main #undef main #endif
3.以下のコマンドを実行し、configureスクリプトについて文句言ってくるのは無視。
runghc Setup.lhs configure runghc Setup.lhs build runghc Setup.lhs install
ターミナルのスクリーンショットのスペースは節約しよう。
gitのインストール、そしてソースコードのクローン
Pro Git本によって、私の使っているmsysgitはgitの基本的な機能を提供している。
“InstallMSysGit” wiki pageに従ってはいけない、何故ならこれはあなたを間違った道*5に導いてしまうからだ。
gitの十分なフルインストーラ(Git-1.7.0.2-preview20100309.exe) をダウンロードし、これをインストールしよう。私はPATH variableについて考えた他は、全てデフォルトの設定を使った。ーー私は"GitをWindowsのコマンドプロンプトから使う(Run Git from the Windows Command Prompt)"を選択している。
そして他の良いビューを手に入れよう。私はKDiff3パッケージをインストールした。これは次に私のインストールするツールと上手く調和している。KDiff3インストーラをダウンロードし、これをインストールしよう。
次はGitExtentionsパッケージ。これはgitを扱うナイスなGUIだ。ここから完璧なインストーラをかっぱらおう。私はこれを全てデフォルトの設定でインストールする。初回実行時、これはあなたのシステムを調べて、使用するのに必要なソフトウェアを検索する。もしあなたが上の通りgitとkdiff3をセットアップしているなら、自動的に検出するはずだ。そしてGitExtentionsのセットアップが終わったら、SettingsウィンドウのGlobal settingsタブに切り替え、ユーザー名とメールアドレスを入力する。Checklistタブに戻って"Save and rescan"ボタンをクリックし、"Ok"ボタンでセットアップは終わりだ。
※原文ではここにGitExtensionsのスクリーンショット
"Clone repository"ボタンをクリックし、http://github.com/tbogdala/ExitStrategy.gitのようなリポジトリをクローンする。私は出力先(destination)に私の標準であるC:\Projectsフォルダを設定した。"Clone"をクリックで、ソースファイルのコピーがダウンロードされる。
※原文ではここにClone操作中の画面のスクリーンショット
gitに関する追加情報、Pro Git本*6はチェックしとこう。The Seeker's Quillによるgitに関する良い記事がある。そしてさらに私が見付け出したlostechiesというブログが助けになる。
ゲームをビルドする
今、これで私たちは前もって必要なソフトウェアをインストールし、プロジェクトのソースコードをコピーした、これで準備は万端・・・
えーと、ちょっと待って。もしあなたの環境で直ちに、cabal configureし、プロジェクトをビルドしようとすると、SDL-imageとSDL-ttfのポイントでどっさりとリンクエラーを得る。Undefined reference to ‘IMG_Load_RW’, undefined reference to ‘IMG_Load’・・・ みたいにね。
※原文ではここで実際にエラーが出ているスクリーンショット
1.exitstrategy.cabalファイルを開き、これらの2行を最後に追加する。
Extra-Lib-Dirs: C:\SDL_image-1.2.10\lib, C:\SDL_ttf-2.0.9\lib Extra-Libraries: SDL_image, SDL_ttf
2.C:\SDL_image\libフォルダーへ移動し、SDL_image.dllのコピーを作成し、 libSDL_image.dll.a (lib*.a)を呼ぶ
3.step2と同じように、C:\SDL_ttf-2.0.9\libフォルダへ移動し、SDL_ttf.dllのコピーを作成し、libSDL_ttf.dll.aを呼ぶ。
そして、プロジェクトをビルドする。
※原文ではここでビルドに成功しているコマンドラインのスクリーンショット
最後のステップは以下のファイルのコピーを作成し、これらをC:\Projects\ExitStrategy\dist\build\exitstrategy-editorに配置することだ。
- C:\SDL-1.2.14\bin\SDL.dll
- C:\SDL_image-1.2.10\lib\jpeg.dll
- C:\SDL_image-1.2.10\lib\libpng12-0.dll
- C:\SDL_image-1.2.10\lib\libtiff-3.dll
- C:\SDL_image-1.2.10\lib\SDL_image.dll
- C:\SDL_image-1.2.10\lib\zlib1.dll
- C:\SDL_ttf-2.0.9\lib\libfreetype-6.dll
- C:\SDL_ttf-2.0.9\lib\SDL_ttf.dll
最後に、コマンドラインでC:\Projects\ExitStrategyから“dist\build\exitstrategy-editor\exitstrategy-editor.exe”を実行すると、成功だ!
※原文ではここにゲーム画面が起動しているスクリーンショット
しかし今、exitstrategy-editor.exeをダブルクリックしても実行されない。何故ならこれはart/64×74配下に何も探しに行かないからだ。(あなたがさらに、このフォルダからビルドディレクトリに全てのdllをコピーすれば話は別だが)。これは未来のバージョンで修正するつもりだ。
この文章はANIMAL-MACHINE.COMのA HASKELL ADVENTURE IN WINDOWSを2012/12/15にブログ主が意訳したものです
Haskellでチャーチ数
色々と記事にしたいネタはあるのですが、どれもちゃっちゃと書ける感じでは無いので、今日はちょっと小ネタ更新します。
前々から、「チャーチ数とかHaskellなら簡単に表現できんじゃね?」とか思ってたのですが、どうやってshowするか思いつかないし考えるのも面倒だから放置していたのですが、つい最近、ふとした拍子にめちゃくちゃ簡単だという事に気づきまして・・・
せっかくなのでちょいと遊んでみましょう。
で、とりあえずまず、いつもの感じでghciを起動して、こんな関数を定義します。*1
Prelude> let showChurch f = f succ 0
で、そのままラムダ式でチャーチ数を書いて、この関数に適用してやります。
Prelude> showChurch ((\f x -> x) :: (t -> t) -> t -> t) --0の場合のみ、明示的に型を指定する必要がある 0 Prelude> showChurch (\f x -> f x) 1 Prelude> showChurch (\f x -> f (f x)) 2 Prelude> showChurch (\f x -> f (f (f x))) 3 Prelude> showChurch (\f x -> f (f (f (f x)))) 4
これが、まぁ、思いつきの割にはすんなりと動いてくれたので、調子に乗ってWikipediaにある定義を色々と実装して遊びます。
まずはsucc
Prelude> let succChurch n f x = f (n f x) Prelude> showChurch $ succChurch (\f x -> f x) 2 Prelude> showChurch $ succChurch (\f x -> f (f x)) 3 Prelude> showChurch $ succChurch (\f x -> f (f (f x))) 4
足し算とか・・・
Prelude> showChurch $ plusChurch (\f x -> f (f (f x))) (\f x -> f (f x)) 5 Prelude> showChurch $ plusChurch (\f x -> f (f (f (f x)))) (\f x -> f (f (f x))) 7
かけ算も元気に動く
Prelude> let multChurch m n f = m (n f) Prelude> showChurch $ multChurch (\f x -> f (f (f (f x)))) (\f x -> f (f (f x))) 12
で、論理記号なんかも実装します。
trueとfalseは何もラムダ式で書くまでもなく・・・
Prelude> let true = const Prelude> let false = const id
andとかorはWikipediaのそのまんま書く。
Prelude> let andc p q = p q false Prelude> let orc p q = p true q
で、同大百科によると、if-then-elseの定義は次のようになっていますが・・・
IFTHENELSE := λp x y. p x y
でもこれいらなくね?いらないよね??
ってな感じで無視してそのまんまいじります。
Prelude> showChurch $ (andc true true) (\f x -> f x) (\f x -> f (f x)) 1 Prelude> showChurch $ (andc true false) (\f x -> f x) (\f x -> f (f x)) 2 Prelude> showChurch $ (andc false false) (\f x -> f x) (\f x -> f (f x)) 2 Prelude> showChurch $ (orc false false) (\f x -> f x) (\f x -> f (f x)) 2 Prelude> showChurch $ (orc false true) (\f x -> f x) (\f x -> f (f x)) 1
あと、述語 iszero はこんな感じ・・・
let iszero n = n (\x -> false) true Prelude> showChurch $ (iszero ((\f x -> x) :: (t -> t) -> t -> t)) (\f x -> f x) (\f x -> f (f x)) 1 Prelude> showChurch $ (iszero (\f x -> f x)) (\f x -> f x) (\f x -> f (f x)) 2 Prelude> showChurch $ (iszero (\f x -> f (f x))) (\f x -> f x) (\f x -> f (f x)) 2
とまぁ、このへんの概念は自分の手で簡約してナンボってのはあるのですけど、実際にPC上でちゃんと動いてる所見るのもなかなか楽しいもんですね。
*1:数値を返してて全然showしてないじゃん!!とかいうツッコミは無しで。今は表示できれば良いんです!!
Freeモナドって何なのさっ!?
最近Haskellerの間でFreeモナドが熱いです。
Haskellで悟りを開いた人がFreeモナドで再び悟りを開いたりして、なんかよく解らないけど凄いことになっている今日このごろですが、すっかり乗り遅れていました。どうも、貴女のちゅーんです。
で、皆こぞって「すごいすごい」と言っているFreeモナドなので、流石にいつもまでも全然知らないのはマズイんじゃないかなぁとか思って、重い腰を持ち上げ調べながらこの記事を書き始めたワケですよ。はい。*1
けっこう急ぎで勉強して書き上げたので随所に間違いあるかもです。ツッコミお待ちしてます。
さて、この「Freeモナド」について、オレオレ定義で簡単に言葉にすると。「Functorと組み合わせて様々な挙動を実現できるモナド」です。
大抵「Monadのインスタンス」というと、MaybeにしてもIOにしても、わりと具体的な事象を扱ってますが、このFreeモナドはそれ自体が抽象的です。
具体的な事象を扱うFunctorと組み合わせてはじめて、具体的な事象を扱うモナドになるワケですね。
先程「Functorと組み合わせて」と書いた以上、当然のごとく、あらゆるFunctorがFreeモナドと組み合わせる事ができるワケですけども、Freeモナドで扱うためには、そのFunctorが、それ自身のFunctorになる構造を基本として考えていく必要があります。
言葉で言うとややこしいので、具体例を示しましょう。
*Main> :t [[[[[[[[]]]]]]]] [[[[[[[[]]]]]]]] :: [[[[[[[[a]]]]]]]] *Main> :t Just (Just (Just (Just Nothing))) Just (Just (Just (Just Nothing))) :: Maybe (Maybe (Maybe (Maybe (Maybe a))))
例のように、その型自体を再帰的に包み込んだFunctorを拡張して扱います。
いずれも[]やNothingが終端になっているので、終端の型変数 a は多態に扱う事ができます。*2
注目したいのは、再帰の深さが深くなるほど、型定義も深くなってしまっている点です。
こういった構造を同じ型として扱えるようにするのが、Freeモナドを理解する第一歩となります。
まず、Freeというデータ型は、次のように定義されます。
data Free f r = Free (f (Free f r)) | Pure r
Pure rはリストの[]とか、MaybeのNothingみたいなもんですね。
では、"Free (f (Free f r))"となっている部分について見てみましょう。
型名とデータコンストラクタが共にFreeとなっていて若干ややこしいので、ちょっと読み替えてみます。次のようにしても等価です。
data FreeT f r = FreeC (f (FreeT f r)) | Pure r
外側のFreeCがデータコンストラクタです、内側のFreeTで、再帰的な型になってるのが解ると思います。
fとFreeTがお互いに包み合ってる、なんだか不思議な構造です。
で、このFree型を、先ほどのリストの例で書くと次のようになります。
*Main> :t Free [Free [Free [Free []]]] Free [Free [Free [Free []]]] :: Free [] r *Main> :t Free [Free [Free [Free [Free [Free []]]]]] Free [Free [Free [Free [Free [Free []]]]]] :: Free [] r
Maybe型の例だとこんな感じ。
*Main> :t Free (Just (Free (Just (Free Nothing)))) Free (Just (Free (Just (Free Nothing)))) :: Free Maybe r *Main> :t Free (Just (Free (Just (Free (Just (Free (Just (Free Nothing)))))))) Free (Just (Free (Just (Free (Just (Free (Just (Free Nothing)))))))) :: Free Maybe r
お!
ネストさせる回数が変わっても型は変わってないぞっ!!
というワケで、型が揃えられたので、晴れてこいつらを使って色々できるようになったワケです。
Nothingを0、JustをSuccとして、自然数に見立てた計算とかもできそうですが、そういう変態的な事をするのは今回の目的では無いのでちゃっちゃと進めます。*3
突然ですが、Freeはモナドです。
今回の記事の題材が「Freeモナド」ですし。
というわけで、Freeモナドの定義は次のようになっています。
instance Functor f => Monad (Free f) where return = Pure Free x >>= f = Free (fmap (>>= f) x) Pure x >>= f = f x
Freeモナドの>>=の動きを、実際に簡約して追ってみましょう。
Maybeの場合
Free (Just (Pure ())) >> Free (Just (Pure ())) == Free (Just (Pure ())) >>= (\_ -> Free (Just (Pure ()))) == Free (fmap (>>= (\_ -> Free (Just (Pure())))) (Just (Pure ()))) == Free (Just (Pure () >>= (\_ -> Free (Just (Pure ()))))) == Free (Just ((\_ -> Free (Just (Pure ()))) ())) == Free (Just (Free (Just (Pure ()))))
つまり、
Free (Just (Pure ())) >> Free (Just (Pure ())) == Free (Just (Free (Just (Pure ()))))
リストの場合
Free [Free [Pure ()]] >> Free [Free [Pure ()]] == Free [Free [Pure ()]] >>= (\_ -> Free [Free [Pure ()]]) == Free (fmap (>>= (\_ -> Free [Free [Pure ()]])) [Free [Pure ()]]) == Free [Free [Pure ()] >>= (\_ -> Free [Free [Pure ()]])] == Free [Free (fmap (>>= (\_ -> Free [Free [Pure ()]])) [Pure ()])] == Free [Free [Pure () >>= (\_ -> Free [Free [Pure ()]])]] == Free [Free [(\_ -> Free [Free [Pure ()]]) ()]] == Free [Free [Free [Free [Pure ()]]]]
つまり、
Free [Free [Pure ()]] >> Free [Free [Pure ()]] == Free [Free [Free [Free [Pure ()]]]]
といった感じで、構造が合成されます。
ぶっちゃけ、これがFreeモナドの全てだったりします。では、「コレを使って何ができんの?」という話をしていきましょう。
ちょとした言語、「挨拶言語」を考えましょう。
この言語は、「Hello!!!」と出力する命令、「Hey!!!」と出力する命令、「GoodBye!!!」と出力して終了する命令、それから年齢を引数に取って、「I'm x years old」と自己紹介する4つの命令を持っています。
ではまず、この構文木のデータを定義してみます。
data MyProgram n = Old Int n | Hey n | Hello n | GoodBye
例えば、これを使って、"Hello!!!" "Hello!" "GoodBye!!!"と表示する場合、こんな構造になります。
*Main> :t (Hello (Hello (GoodBye))) (Hello (Hello (GoodBye))) :: MyProgram (MyProgram (MyProgram n))
"Hey!!!" "GoodBye!!!" と表示したい場合、こんな構造になります。
*Main> :t (Hey (GoodBye)) (Hey (GoodBye)) :: MyProgram (MyProgram n)
困ったことに、それぞれ型が違ってしまっているので、このままではこの構文木を実行するためのrunProgram関数が実装できません。
そこで、先ほどのFreeを使って、型を揃えてみましょう。
まずはFunctorにしてやります。
instance Functor MyProgram where fmap f (Old o n) = Old o (f n) fmap f (Hey n) = Hey (f n) fmap f (Hello n) = Hello (f n) fmap f GoodBye = GoodBye
でもって、Freeで型を整えます。
*Main> :t Free (Hello (Free (Hello (Free GoodBye)))) Free (Hello (Free (Hello (Free GoodBye)))) :: Free MyProgram r *Main> :t Free (Hey (Free GoodBye)) Free (Hey (Free GoodBye)) :: Free MyProgram r
これで、この構造を順に解析して、処理を実行するrunProgram関数を実装できますね。Pureが来た場合は"return"を表示する事にしておきます。
runProgram :: Show r => Free MyProgram r -> IO () runProgram (Free (Old o n)) = putStrLn ("I'm " ++ show o ++ " years old") >> runProgram n runProgram (Free (Hey n)) = putStrLn "Hey!!!" >> runProgram n runProgram (Free (Hello n)) = putStrLn "Hello!!!" >> runProgram n runProgram (Free GoodBye) = putStrLn "GoodBye!!!" runProgram (Pure r) = putStrLn $ "return " ++ show r
実行結果:
*Main> runProgram $ Free (Hello (Free (Hello (Free GoodBye)))) Hello!!! Hello!!! GoodBye!!! *Main> runProgram $ Free (Hey (Free GoodBye)) Hey!!! GoodBye!!!
Freeはモナドなので、 >> で処理を繋げながら実行できます。
*Main> runProgram $ (Free (Hello (Pure ()))) Hello!!! return () *Main> runProgram $ (Free (Hello (Pure ()))) >> (Free GoodBye) Hello!!! GoodBye!!! *Main> --勿論do構文でも使えます *Main> :{ *Main| runProgram $ do { *Main| Free $ Hello (Pure ()); *Main| Free GoodBye *Main| } *Main| :} Hello!!! GoodBye!!!
毎回Freeを意識しながら書くのも大変なので、関数にしてしまいましょう。
liftF :: Functor f => f r -> Free f r liftF cmd = Free (fmap Pure cmd) old :: Int -> Free MyProgram () old o = liftF (Old o ()) hey :: Free MyProgram () hey = liftF (Hey ()) hello :: Free MyProgram () hello = liftF (Hello ()) goodBye :: Free MyProgram () goodBye = liftF GoodBye
後は書くだけです。
--サブルーチン sub :: Free MyProgram () sub = do hello old 25 pg :: Free MyProgram () pg = do hey sub --サブルーチン呼出し hey goodBye main :: IO () main = runProgram pg
実行結果:
Hey!!! Hello!!! I'm 25 years old Hey!!! GoodBye!!!
たったこれだけの定義で独自のモナドになってしましました!
関数定義も分岐も再帰も、Haskellの機能そのまま使えば良いですし、do構文で手続き的な記述ができますし、処理する側でIOや副作用の提供も自由自在です。
runProgramは構文を順に追って好きな時に実行できるので、キーが入力されるまで待機したり、その間もバックグラウンドでアニメーション動かしたり、出来ること上げていくとキリが無い感じです。
てなワケで、「Functorと組み合わせて手軽に独自のモナドに拡張できるFreeモナド」のお話でした。*4
ゲームを作るにあたって、継続モナドを使って実装しようとしていた事が、Freeモナドならわりと簡単に実装できそうな感じですし、チーム開発でちょっとした開発フレームワークを組み立てるのにも役立ちそうです。
【参考資料】Haskell for all: Why free monads matter
http://www.haskellforall.com/2012/06/you-could-have-invented-free-monads.html?m=1
関数型プログラミングの実用性だとか将来性だとか
に、ついて語ろうとすると、どうしても漠然とした話になってしまって、なかなか切り出せないのですが・・・
ちょっと今日、こんな記事を見つけまして。
【開発閑話】文系プログラマが関数型言語(とラムダ計算)を理解してみる【初心者向け解説】
「ご指摘やご感想等あればどうぞお寄せください。」という事ですし・・・ふふふ・・・大人げないなぁとか思いつつ、我慢できなかったのでちょっと記事を書かせて頂きますよん。
あのですね。
『心者向け』解説と題して、『中途半端な理解』(ならまだしも適当な理解)で関数型のネガティブな面を強調した記事を書くのは如何なものかと思うのですよ。
万が一でも、この記事を読んで閑数プログラミングの世界から足を遠ざける人が居たらイヤだなぁと。
その辺、いくらか配慮しては居るらしく、「研究材料としては興味深い」みたいな事仰ってるけど、ぶっちゃけフォローになってないです。
関数型言語の敷居を下げて、もっと普及させたいと思っている身としては、色々とモニョモニョしてしまう内容です。
かといって、細かい所を一個一個指摘していこうかと思うと、ちょっと突っ込みどころ多すぎて記事にしていると日がくれてしまうので、要点3つに纏めさせて頂きます。
要点その1、ラムダ式に関する間違い
…あれ、プログラミングできなくね?と思ったあなたは正解。
ラムダ式の機能そのものには、プログラミングに必要な基本的な機能がほとんどありません。
不正解です。
確かに手続き言語でラムダ式を扱う場合は「無名関数を定義するための構文の一つ」と捉えて問題無いかもしれませんが、ラムダ計算そのものはそれ自体がチューリング完全な計算モデルです。
つまり、純粋なラムダ計算でC言語と同等な表現力を持ちます。
理論上はこれだけでC言語のコンパイラやインタプリタを作ることもできる。と言うと解りやすいでしょうか。
もっとも、画面やキーボード等の入出力(IO)についてはちょっと特殊な話になって来て、濃ゆい関数型プログラマーの間でも認識が別れる部分なので、ここでは深く触れません。
要点その2、関数型プログラミングの設計は難しいのか
しかし詳細は後述しますが、関数型言語を利用すると、ほとんどの開発案件ではプログラム設計の難易度が飛躍的に上がり、開発効率が低下します。
これは開発工数の増大に直結するため、現実的な方法とは言えません。また現状の関数型言語においても、厳密にバグが無いことを証明することは容易ではありません。
「プログラム設計の難易度が飛躍的に上がる」事を説明した「詳細」がこの後の文章の何処にあるのかちょっと解りませんでしたが、関数型プログラミングにおいて「設計の難易度が飛躍的に上がる」と考えていらっしゃるなら、自分の考え方とは異なります。
自分は「設計の重要性は上がる」と考えていますが、「設計の難易度が上がる」とは思いません。
そもそも何を持って「設計の難易度が上がる」と考えているのか存じ上げませんが、もし「関数型言語は表現力が乏しいから」と考えているのなら、それは間違いです。
ある言語が「関数型言語」と呼ばれるためには、関数がファーストクラスオブジェクトである事が大前提なワケですが、この事は、それだけでその言語が「高度な抽象化」を可能にしていると言って良いほど重要なポイントです。
記事を読ませて頂いていると、どうも「map関数」を言語固有の機能のような解釈をされているようなのが気になりました。
map関数やfold関数のような、高階関数と呼ばれている関数は、あらゆる関数型言語を使って、簡単に再実装できます。
これらの関数自体が、その言語の持っている「表現力の賜物」だと思えば、少なくとも関数型言語が「複雑で柔軟なプログラムが表現できない」とは言えないでしょう*1。
ついでに言うと、関数型プログラミングでは各関数を宣言的に記述するスタイルが基本なので、再利用性が高くリファクタリングが容易です。
つまり、メンテナンス性がめっちゃくちゃ高いです。・・・この点については、これ以上は言いますまい。
もっとも、仰っている「設計」というのが、我々SI屋の大大大大大好きな、役に立つのか立たないのかイマイチ良く解らない「設計書*2」とか言うものを書くことであるとするなら、それは確かに難しい事かもしれませんが・・・
要点その3、関数型言語はまだ実用段階に無いのか
このため現在のところ、関数型言語は実用的なプログラム言語としては受け入れにくい状況にあると言えます。まだ研究段階の思想といえるでしょう。
この辺は、半年ほど前に第一回関数型言語勉強会に参加した時点では足踏みしていたのですが、今なら断言しましょう。
関数型言語・・・という区分けは大雑把すぎるので、そうですね、純粋関数型プログラミング言語Haskellは十分実用に耐えうるプログラミング言語です。
ここ最近、Haskell×SDLの記事を何度か更新していましたが、理由は勿論(詳細はいくらか形になるまで公開しませんが・・・)、この言語を使ってわりと本格的な2Dゲームの開発に取り掛かっているからです。
最後の更新は9/9ですが、作業自体は徐々に進行していますし、その過程で「イケる!」という確信を掴んでたりとかします。
自分の場合はゲームでの事例ですが、Haskellを用いたWeb開発用フレームワークなんかもありますし、Scalaを業務に役立てている方もチラホラ居らっしゃるようです。
「関数型言語は研究段階」という色眼鏡は、もうそろそろ、捨てても良いんじゃないですか?
Haskellでゲーム開発 - SDL-Imageで画像を表示
とりあえず、ゲームを作るなら画像が描画できなくては話になりませんね。
というワケで、どうやってSDLで表示した画面に画像を表示するかという話を書きます。
もともとSDLにはBMPを操作するための関数が用意されていたりしますが、対応してるフォーマットがBMPだけなんですね、コレ。
少なくとも、キャラクターを背景にかぶせた描画はしたいです。そういう事を色々考えると透過色が使える画像フォーマットが便利という事になります。
で、透過PNGとかGIFとかJPGとか使うためには、SDL-Imageという別のパッケージをインストールして使う必要があるんですね。
cabalからだとこんな感じに見えるヤツです。
$ cabal list sdl-image * SDL-image Synopsis: Binding to libSDL_image Default available version: 0.6.1 Installed versions: 0.6.1 License: BSD3
コイツをインストールしてやって、SDLとは別でimportしてやらないといけないというワケです。
というわけで、ここから先はSDLとSDL-Imageがインストールされていて、ghcから使えるようになっている前提で話を進めます。
以下の三枚の画像を用意して、hsファイルと同じ場所に設置しましょう。
OnpuB.png:
OnpuG.png:
OnpuR.png:
見ての通り、「おんぷ」です。
形が微妙に歪んでるとか言わないでください。マウス描きなんです。気合入れて描きました。
ちゃんと透過色を透明に処理してくれているか確認するために、この3枚を少しづつずらして重ねながら描画しましょう。
下のようなソースコードになります。
module Main where import Data.Word import qualified Graphics.UI.SDL as SDL import qualified Graphics.UI.SDL.Image as SDLi gameinit :: IO () gameinit = do SDL.init [SDL.InitEverything] SDL.setVideoMode 640 480 32 [] return () dispImage :: IO () dispImage = do --準備 screen <- SDL.getVideoSurface imgR <- SDLi.load "OnpuR.png" imgG <- SDLi.load "OnpuG.png" imgB <- SDLi.load "OnpuB.png" --表示 SDL.blitSurface imgR (Just $ SDL.Rect 0 0 100 100) screen (Just $ SDL.Rect 245 165 100 100) SDL.blitSurface imgG (Just $ SDL.Rect 0 0 100 100) screen (Just $ SDL.Rect 270 190 100 100) SDL.blitSurface imgB (Just $ SDL.Rect 0 0 100 100) screen (Just $ SDL.Rect 295 215 100 100) SDL.flip screen --画像を解放 SDL.freeSurface imgR SDL.freeSurface imgG SDL.freeSurface imgB --画面が閉じられるまで再帰 e <- SDL.waitEvent if e == SDL.Quit then return () else dispImage main :: IO () main = gameinit >> dispImage >> SDL.quit
再帰の度に画像読み込んで描画して閉じてとかやってるのは、うん、気にしないでください。
テストプログラムなので関数毎の関係を明確にしたかったため、一気にガーっと書いたのでこうなってます。
物凄く手続き的でHaskellっぽく無いです。
問題なく画像表示できているようです。
というわけで、はじめて出てきた関数をちゃちゃっと見ていきましょう。
まず、getVideoSurfaceです。
*Main> :t SDL.getVideoSurface SDL.getVideoSurface :: IO SDL.Surface
前回もちょっと出てきたSurface型ですが、どうやらコレはメモリ上のグラフィック領域へのアクセスを管理するための構造体みたいですね。
typedef struct SDL_Surface { Uint32 flags; SDL_PixelFormat *format; int w, h; Uint16 pitch; void *pixels; /* clipping information */ SDL_Rect clip_rect; /* Reference count -- used when freeing surface */ int refcount; /* This structure also contains private fields not shown here */ } SDL_Surface;
定義を見る感じでは、pixelsというポインタの先に、他のメンバで定義されたフォーマットに応じたグラフィックスデータがあると考えれば良さそうです。外から使う分には特にここまで見る必要は無いですけど。
で、getVideoSurfaceはその名の通り、画面のサーフェスを返す関数です。
表示する画面もグラフィックなので、サーフェスとして保持されているワケですね。
最初のコードでは、screenという変数がgetVideSurfaceの戻り値に束縛されます。
続いて、SDL-Imageパッケージのload関数で、画像の読み込みを実行します。
*Main> :t SDLi.load SDLi.load :: FilePath -> IO SDL.Surface
型定義を見れば一発なので、説明不要でしょう。
いよいよ、サーフェスからサーフェスに画像を転送する処理を書きます。
今回の主役、blitSurface関数の登場です。
複雑と言うほどでは無いですが、ちゃんと書くとちょっとだけ長丁場なので区切り入れます。
*Main> :t SDL.blitSurface SDL.blitSurface :: SDL.Surface -> Maybe SDL.Rect -> SDL.Surface -> Maybe SDL.Rect -> IO Bool
最初の2つの引数が、「転送元」の情報。
残りの2つの引数が「転送先」の情報。
最後のBool型の戻り値なのですが、これ、Hackageにはちゃんと説明無いのでちょっと困りました。
以下が引用ですが、コレだけっきゃ書いて無いんですねー(´・ω・`)
blitSurface :: Surface -> Maybe Rect -> Surface -> Maybe Rect -> IO BoolSourceThis function performs a fast blit from the source surface to the destination surface.
「元サーフェスから先サーフェスへ高速転送を行う」とだけしか書かれていないのですが、どうやらラップ元の関数はint型を返すようになっているらしく、SDLのWikiには次のように書かれています。
Return Value
Returns 0 if the blit is successful or a negative error code on failure; call SDL_GetError() for more information.
何かエラーがあったら負数のエラーコードを返し、SDL_GetError()関数でエラーの具体的な内容を得られる、という事みたいです。
HaskellにもちゃんとgetError関数がラップされています。
*Main> :i SDL.getError SDL.getError :: IO (Maybe String) -- Defined in Graphics.UI.SDL.General
普通に実行してこの戻り値を監視していると、常にTrueを返しているようなので、Falseが返ってきたら転送失敗なので、この関数を使ってエラー内容を取得する・・・みたいな使い方であってると思います。
・・・思います。
話を戻して、第二引数と第四引数のRect型ですが、これは転送元先のサーフェスから切り出す「範囲」を指定するための構造体みたいですね。
定義は次の通り、左上位置と大きさを表す四値だけ持ってるシンプルな型です。
*Main> :i SDL.Rect data SDL.Rect = SDL.Rect {SDL.rectX :: Int, SDL.rectY :: Int, SDL.rectW :: Int, SDL.rectH :: Int} -- Defined in Graphics.UI.SDL.Rect instance Eq SDL.Rect -- Defined in Graphics.UI.SDL.Rect instance Ord SDL.Rect -- Defined in Graphics.UI.SDL.Rect instance Show SDL.Rect -- Defined in Graphics.UI.SDL.Rect
つまるところ、blitSurfaceを日本語で長たらしく説明すると・・・
第一引数のサーフェスから、第二引数の範囲を切り出して、第三引数のサーフェスの第四引数の範囲に転送を行い、処理が失敗した場合はFalseを返却する。
というような感じになるのでしょうか。
といっても、使い方が分かればそれほど難しい事は無さそうです。
画面へ画像の転送が終わったら、今度はflip関数で画面の更新をしてやります。
*Main> :t SDL.flip SDL.flip :: SDL.Surface -> IO ()
この関数は、ハードウェアでダブルバッファをサポートしていない場合、updateRectと同等の動きをします。
*Main> :t SDL.updateRect SDL.updateRect :: SDL.Surface -> SDL.Rect -> IO ()
ハードウェアがダブルバッファリングをサポートしていて、かつsetVideoMode関数でダブルバッファを使うよう初期化すると、ハードウェア側によるバッファ切り替えが行われます。
とにかく、画像転送処理後、画面全体を再描画するには、flip関数を呼び出せば良いと考えれば良いみたいです。
これらはあくまでC言語のライブラリをラップしてるダケなので、最後に使い終わったリソースはちゃんと解放してやらないとメモリが大変な事になります。
というわけで、サーフィスの解放には、freeSurface関数を使います。
*Main> :t SDL.freeSurface SDL.freeSurface :: SDL.Surface -> IO ()
これも型定義を見れば直ぐに解ると思いますので、これ以上のグダグダ書くことも無いです。
あとはこれらの処理を前回のメインループ内に上手く組み込んでやれば、アニメーション処理なんかもできるハズ・・・