boost::logはmutable_constantをshallowコピーする

boost::logのloggerとそれに紐付いたmutable_constantをコピーしようとしてハマったのでメモ。
deepコピーがしたかったのだけど、結果はタイトル通りshallowコピーだった。ドキュメントにもよく読んだら書いてあった。

The shared pimpl design comes significant in a few cases though. One such case is copying the attribute. The copy operation is shallow, so multiple interface objects may refer to a single implementation object. There is no way to deep copy an attribute.

http://www.boost.org/doc/libs/1_57_0/libs/log/doc/html/log/detailed/attributes.html

clone()とかあれば便利なんだけどなさそうだし、どうしてもしたければ自前でmutable_constantを再生成して登録するぐらいしかないのかな・・・

テストコード

#include <iostream>
#include <boost/log/sources/logger.hpp>
#include <boost/log/attributes/mutable_constant.hpp>
using namespace std;
namespace lg = boost::log;

int main(int argc, char* argv[]) {
	//このmutable_constantをコピーすると内部変数はどうなるのか?
	lg::attributes::mutable_constant<int> attr1(1);
	cout << attr1.get() << endl;

	//mutable_constantのコピーは内部変数をshallowコピーする
	lg::attributes::mutable_constant<int> attr2(0);
	attr2 = attr1;
	attr2.set(2);
	cout << attr1.get() << " " << attr2.get() << endl;

	//mutable_constantをloggerに登録
	lg::sources::logger log1;
	log1.add_attribute("attr", attr1);

	//loggerのコピーは保持するmutable_constantをshallowコピーする
	lg::sources::logger log2;
	log2 = log1;
	lg::attributes::mutable_constant<int> attr3 = lg::attribute_cast<lg::attributes::mutable_constant<int>>(log2.get_attributes().find("attr")->second);
	attr3.set(3);
	cout << attr1.get() << " " << attr2.get() << " " << attr3.get() << endl;

	return 0;
}

実行結果

1
2 2
3 3 3

HaskellでのSDLの使い方

SDLHaskellバインディングはいくつかありますが、この記事ではHackageのSDLを使って説明しています。Windows環境だとCabalではうまく入らなかったりするので、直接ソースからビルドする必要があるかもしれません。

SDLを使うにはFFIが必要

SDLを使ったHaskellコードを単純に

$ ghc -package SDL --make Main.hs

のようにコンパイルしても、SDL_mainが見つからないなどのエラーが出てコンパイルできません。SDLを使ったプログラムでは本来、ユーザが作成したmain関数はSDL.hのマクロによってSDL_mainにリネームされ、SDLライブラリの内部にある本当のmain関数がSDL_mainを呼び出すことになってるのですが、ghcはその面倒まで見てくれないのでSDL_mainが作られず、エラーになってしまいます。

これを回避するためには、C言語で書いたmainの中からFFIHaskellを呼び出した上で、元のmainをSDL.hでSDL_mainに書き換えさせる必要があります。

Haskellの関数をエクスポートする

まずは画面を表示するだけのHaskellコードを書いて、Cのmainから呼び出すためのhs_mainをエクスポートします。

{-# LANGUAGE ForeignFunctionInterface #-}
module HsMain where

import qualified Graphics.UI.SDL as SDL

foreign export ccall "hs_main" hs_main :: IO ()

hs_main :: IO ()
hs_main = do
    SDL.init [SDL.InitEverything]
    SDL.setCaption "SDL test" ""
    SDL.setVideoMode 300 300 32 []

    mainLoop
    SDL.quit

mainLoop :: IO ()
mainLoop = do
    e <- SDL.waitEvent
    case e of
        SDL.Quit -> return ()
        _ -> mainLoop

ここで、このコードをそのまま

$ ghc --make HsMain.hs

のようにコンパイルしようとすると、HsMain_stub.cとHsMain_stub.hというファイルが生成されます。これがHaskellコードをCから呼び出すためのインターフェースになります。

Cから呼び出す

次に、このインターフェースを通してHaskellコードを呼び出すCコードを用意します。

#include <SDL.h>

#include "HsFFI.h"
#ifdef __GLASGOW_HASKELL__
#include "HsMain_stub.h"
extern void __stginit_HsMain(void);
#endif

int main(int argc, char* argv[]) {
	hs_init(&argc, &argv);

#ifdef __GLASGOW_HASKELL__
	hs_add_root(__stginit_HsMain);
#endif

	hs_main();
	hs_exit();
	return 0;
}

最後に、これらを合わせて

$ ghc -package SDL --make -no-hs-main HsMain.hs c_main.c

のようにコンパイルすると、実行ファイルを生成することが出来ます。

実行結果


GHCFFIに関する詳しいドキュメントはこの辺にあります。

Haskellでシューティングゲームを作ったまとめ

ゲーム製作者コミュニティ in 札幌の春のシューティング祭りプロジェクトで一ヶ月ほどかけて簡単なシューティングを作りました。
Windowsバイナリは紹介ページ、ソースはHackageから落とせます。ビルドにはSDLが必要です。また、絵と音楽はそれぞれコミュニティのぐぐぐさん、RE-Ya!@D.J.無理王さんに頼みました。


感想

まず、全体的にバグはほとんど出ませんでした。唯一ハマったのはIntのオーバーフローぐらいで、それもSDLとのやり取りのためにIntを使っているので仕方ないかな、という感じです。

IOもそれほど障害にはなりませんでした。面倒なのは事実ですが、do記法を使えば簡単に処理できるのですぐに慣れることが出来ました。ただ、始めは非IO関数として定義しようと思っていたものを、乱数を使いたいとかいう雑多な理由で後からしぶしぶIOにするというケースは結構ありました。

一番困ったのは、テトリスの方も書かれていますが、フィールドラベルが関数としてグローバルに出てしまうことでした。後から見ると実際に衝突しそうなのはそれほど多くないにも関わらず、衝突への恐怖や関数名の一貫性のために、フィールド名が不必要に長くなってしまいました。フィールド名に限らず、Haskellではモジュール単位でしか見せる・見せないを決められないので、全般的にスコープが大きすぎるということが多いように思いました。

このあたりのことを考えると、オブジェクト指向言語のクラスが関数に適当なスコープを与えるために存在するように思えてくるほどで、少し違いますがF#のパイプ演算子なんかも(自分で定義すれば作れますが)なかなか羨ましいと思いました。

今回はしなかったこと

起動画面やゲーム画面は型クラスに出来そうでしたが、気づいたときにはほぼ完成していたので結局しませんでした。また、ゲームのリソースなどはモナドとかに出来そうな気がしましたが、相当難しそうだったのでしていません。

まとめ

Haskellを使うことの一番の利点は、IOなどを除いて状態が無いためにバグが少なくなることだと思います。特に、変数への再代入が無いことでプログラムの見通しがとても良くなります。
RubyPythonには関数型言語の要素がたくさん入っていますが、mapや内包表記だけではなく、もっと定数を積極的に使ってみるのも実践的でいいかもしれません。