free-gameはGlossを参考して作られた2Dグラフィックスライブラリで、GlossライクなDSL風の構文と、実装にFreeモナドを使用していることが特徴。
元々はfree-gameをラップするDSL的なものを作ろうと思っていたのだけど、構文を見た結果、わざわざ自前でパーサーを書いてDSL化まではしなくていいと判断。
ただ、スプライトの定義式がいまいち気に入らない。
font <- loadFont "examples/VL-PGothic-Regular.ttf"
-- 色と座標を指定してテキストスプライトを生成
translate (V2 300 300) $ color black $ text font 30 "A"
こんな風にスプライトの座標変換関数から逆算して書かないといけないので、いまいち読みづらい。
Haskellの構文上しょうがないのだけど、どうにかDSLらしくいい感じに書けるようにしたい。
使用環境
- GHC 9.6.7
- free-game 1.2
- Ubuntu 24.04.2 LTS(WSL上)
GHCの最新版では依存ライブラリのJuicyPixels-utilがビルドできないため、stackなどでバージョンを調整する必要があります。
なお、free-gameの関数はGlossと似ているので、本記事の内容はGlossの使用にも生かせると思います。
先に結論から
こう書くのが正解。
text font 30 "test" &
color green &
translate (V2 220 300)
base-4.8.0.0から、$演算子をflipした&演算子というものが実装されており、Data.Functionをインポートすることで使用できる。
以下、それ以外に試した方法について。
Stateモナド
rotateRS :: (Affine p, MonadState (p a) m) => Double -> m ()
rotateRS = modify . rotateR
rotateDS :: (Affine p, MonadState (p a) m) => Double -> m ()
rotateDS = modify . rotateD
scaleS :: (Affine p, MonadState (p a) m) => Vec2 -> m ()
scaleS = modify . scale
translateS :: (Affine p, MonadState (p a) m) => Vec2 -> m ()
translateS = modify . translate
thicknessS :: (Picture2D p, MonadState (p a) m) => Float -> m ()
thicknessS = modify . thickness
colorS :: (Picture2D p, MonadState (p a) m) => Color Float -> m ()
colorS = modify . color
blendModeS :: (Picture2D p, MonadState (p a) m) => BlendMode -> m ()
blendModeS = modify . blendMode
-- 使用例
(`execState` (text font 30 "test")) $ do
colorS red
translateS (V2 260 300)
スプライト変換用の関数をmodifyでオーバーラップしてStateモナドを使用する方法。
途中で型が変化しない(できない)点がメリットだが、putなどが途中で入るとステートを全部書き換えられてしまうのが難点。
型制約については既に呼び出し側で保証されているため、今回のケースには合わないと判断。
$をflipする
flip ($) (text font 30 "test") $
color blue >>>
translate (V2 240 300)
>>>はCategory用の演算子だが、->がCategoryなので関数合成にも使用できる。
ただし関数適用には別の方法を使用しないといけないので、いろいろと中途半端。
最後に
型制約の強い言語なのでなんとなくいい感じにできるかなと思っていたのですが、同じ処理でも記述のパターンがかなり多く、意外と可読性のライブラリ依存度が高いなと感じました。
「関数型言語だから何をやっても大丈夫!」とはいかないようです。
まあContとかあるしね…