Foreign function interface (FFI)
昨日適当なテトリスが出来上がったけど、
まだまだHaskellネタが続くのです。
現時点であと二つほどは書きたいネタがあったりする。
Haskell上でのゲームプログラミングの方法論、
端的に言うとエレガントさの追求はまた今度。
とりあえず昨日までwxHaskellをやって、
それでゲームを作ったわけであるのだが、いくつかの点で限界を感じた。
- 描画速度・描画機能
- 音が出せない
普通にGUIアプリを作る分には良いんだろうけど、
(GUIのコントロール色々あるしね)
どうにもこうにもゲームには向いてないような印象だ。
そんなわけでSDLを使おうという話になった。(私の中で…)
Haskellというある意味異端なものを使いながら
結局超標準的なSDLに収まるのもなんともかんとも言えないが、
とりあえずやってみないと分からないのである。
もともと全く関数的でないライブラリをもとに
きちんと宣言的なインターフェースを用意できるのかどうか
ということにもそれなりに興味があるし。
で、まずはGoogleでHaskell/SDLの存在を検索してみる。
有ったらそれ使えるので。だがまぁ、とりあえず存在しないらしい。
http://www.libsdl.org/languages.php
ここから調べるところろによると、公式にも認知されていないようである。
というわけで、FFIの勉強を始めることにする。
恥ずかしながらHaskell暦は1年弱あるにもかかわらず
(積極的に使い始めたのはここ数ヶ月だけど)
FFIははじめてである。初めてなのでとんちんかんなことを
書いていても怒らないように。(こっそり教えたってください)
まぁ、wxHaskellも初めてだったんだけど。
- Call Haskell code from C
まずは、HaskellコードをCから呼び出し。
多分使わないと思うんだけど…GHCのユーザーガイドの
FFIのとこの最初に載ってるから…。
まず、以下のようなfoo.hsを用意。
Haskell側の関数の実装。ドキュメントから引っ張ってきただけだが。
module Foo where foreign export ccall foo :: Int -> IO Int foo :: Int -> IO Int foo n = return (length (f n)) f :: Int -> [Int] f 0 = [] f n = n:(f (n-1))
foreign export のところがエクスポートする指定か。
エクスポートできるのはIOモナドだけっぽい。
まぁ、どっちでもあまり変わらないけど。
続いて呼び出し側のC。main.cというファイルにしてある。
#include#include "foo_stub.h" int main(int argc,char *argv[]) { hs_init(&argc, &argv); printf("%d\n",foo(2004)); hs_exit(); return 0; }
コンパイル方法。
ghc -cpp -fffi -o foo.exe foo.hs main.c
なんかよくわからんのだが、-fffiをつけると
FFIなコードがコンパイル出来るようである。
つけないとコンパイルとおらない。
で、FFIなコードをコンパイルすると、普段作られる
foo.hi,foo.oのほかにfoo_stub.cとfoo_stub.hが作られて、
さらにそれのコンパイルまで行われる。
なんだか至れり尽くせりな感じである。
そのスタブなヘッダをmain.cからincludeする形となっている。
それでとりあえずfoo()が呼び出せるようだが、
最初にhs_init()とhs_exit()を呼び出す必要がある。
(本当に要るのか?と思って省いてみると見事に落ちた)
>foo 2004
で、実行すると普通に動いた。というか、こんだけじゃ
ほんとに呼び出してるかわからんぞ???
もうちょっと複雑なnqueenでも動かさしてみようかの?
nqueen :: MonadPlus m => Int -> m [Int] nqueen n = inner 0 [] where inner cur ls | n==cur = return ls | otherwise = msum [inner (cur+1) (n:ls) | n <- [1..n], canPut n ls] canPut n ls = not $ n `elem` (zip [1..] ls >>= \(d,n) -> [n,n-d,n+d])
MonadPlus版。
これ一つでリストモナドを使えば全解リスト、
Maybeモナドを使えば一つの解を見つけるスグレモノである。
headとりゃいいやん、とか言っちゃ駄目。
というわけで、こんなソースをでっち上げた。
module Queen where import Control.Monad nqueen :: MonadPlus m => Int -> m [Int] nqueen n = inner 0 [] where inner cur ls | n==cur = return ls | otherwise = msum [inner (cur+1) (n:ls) | n <- [1..n], canPut n ls] canPut n ls = not $ n `elem` (zip [1..] ls >>= \(d,n) -> [n,n-d,n+d]) foreign export ccall queenCnt :: Int -> IO Int queenCnt :: Int -> IO Int queenCnt n = return (length (nqueen n))
queenCntがエクスポートする関数で、解答の数を返す。
C側は
#include#include #include "queen_stub.h" int main(int argc,char *argv[]) { hs_init(&argc, &argv); printf("%d\n",queenCnt(atoi(argv[1]))); hs_exit(); return 0; }
適当にこんな風に変えた。
コンパイル&実行。
>queen 8 92 >queen 10 724
よしよし、これで満足である。(満足なのか…)