自己紹介
ハッピーホリデー!
42Tokyo Advent Calendar 2023 の 10日目を担当する、42Tokyoのふるまい(komatsud)です。
入学が2023年1月なので知らない人も多いでしょう。
はじめまして〜!
42Tokyoでは、6月ごろに勝手に42電子工作部というアソシエーションを作りまして、今は次のリーダーを募集中です。
プログラミング完全未経験の状態で入学して、1年経ちました。くらいの温度感です。
昨日の記事
昨日は @corvvs さんが暗号学的ハッシュ関数 門前を書いてました!!
ハッシュ関数興味あったからめっちゃ楽しく読みましたよ〜!
Haskellで楽しむ抽象的なプログラミング
この記事の目的は、ユーザー視点で、Haskellで実際にプログラムを作ることを楽しんでもらうことです。
Haskellの興味深さ
Haskellの面白い側面はいろいろあると思います。
関数型言語であることもいい。数学的な圏論から見ても面白く、コンパイラも面白い。思想がまず面白い。
しかしそういった諸側面は、それぞれがベタベタに依存しあっており、厳密に分割できるものではないはずです。
ある目標はある思想から出発していて、また別のある思想がその目標のための手段となる発想を提供し、一つのある仕様となる、といった調子です。
そこで、今回は思い切って、実際にユーザーの視点で、プログラムをデザインするところからHaskellを見つめ直してみようと思います。
そして、ユーザーの視点から見つめなおした時、Haskellの様々な優れた側面は、「目的物の適切な抽象化(適切な!?)」として現前するのではないかな、と思い、この記事を書くに至りました。
Haskellでプログラミング
言語の勉強のためにせっかくだから何か作りたい、けど特に何もない。
というときに、ミニゲームとか作っておくと嬉しいですよね。
だって遊べるし。
なので今回は、とりあえずゲームを作ることを軸に書きます。
Haskellでプログラムを作る嬉しさ
Haskellでゲームを作る嬉しさは実はかなりあります。
わたしは普段の生活でも「は?それ先に言えよ!!」とよく言うと思うのですが、
コンパイルエラーが少なく、実行時になってエラーをドカドカ出してくるシステムが軒並み嫌いです(なぜならばどう見ても正しく動作しないコードはコンパイルの時点で構文エラーとして示されてほしいので!)。
毎回修正して、実行して、動作確認して〜ということはあまりしたくないですよね。
Haskellはコンパイルさえ通ればほとんど自分の思った通りに動作します。
最初は構文に悩んで10時間もコンパイルが通らなかったこともありましたが、慣れれば一発で通るようになります。
GHCのエラーメッセージは結構何言ってるかわかりにくいんですが・・・慣れれば何言われてるかわかってきます。
たまーにパターンマッチのガードを忘れて実行時に例外が飛んでくることもありますが、それくらいですね。
目的物の抽象化
しかし、関数型でプログラムを作るとき特有の苦しみ(楽しみ)があります。
目的物をどう抽象化するかが問題になるのです。
抽象化の出来不出来でコードの美しさが決まってしまいます。いやまあ、なんでもそうなんだけどさ・・・
諦めて全部do構文にゴチャゴチャ詰めて書きますか?
・・・本当にそれでいいんですか!?
自分の心に正直になりましょう。あなたはめちゃくちゃ美しい抽象化をやって、めちゃくちゃ美しいコードを書きたいと思っているはずです。
「動けばいい!」ならHaskellを使う必要なんて一つもないのですから。
美しい抽象化をやってくれる2D描画ライブラリ、gloss
しかし、目的物の抽象化・・・と言っても、ピンとこないかもしれません。
Haskellの2D描画ライブラリに、glossというものがあります。
一旦これを見てみてくださいよ!
どんなコードになるのか見てみよう
以前わたしはこのglossライブラリを使って、簡単なカラーピッカーを作りました。
このアプリのmain関数を一旦見てみましょう。
main :: IO ()
main = do
info <- initInfo
playIO window white 60 info drawWindow keyHandler stepInfo
ね。美しいでしょう。これだけですよ。
このplayIO
というglossの関数に秘密があります。
glossのplayIO関数とは
こちらからドキュメントを確認できます。
定義を見てみましょう〜。
つまり、
- 背景色を受け取ります。
- ゲームのフレームレートを受け取ります。(描画も処理も同じフレームレートになる)
- 「ゲームの初期状態の情報」を受け取ります。
- 「ゲームの情報」を使って、画面を描画する関数を受け取ります。
-
Event
(マウスの動きやキー入力など)を受け取った時、「ゲームの情報」を変更する関数を受け取ります。 - 1回の処理につき、毎回「ゲームの情報」を変更する処理を行う関数を受け取ります。
- 現実世界にIOを返しまーす!
という”ゲーム”の抽象化。
これすごいよくないですか!?
確かにほぼ全ての描画プログラムはこう抽象化できるな、と思って感動しました。
「ゲームの情報」として、レコード構文などを使って直積型のデータをどかんと渡してあげると、例えばメニュー画面とフィールド画面の切り替えなどもできます。柔軟さがすごい。
ゲームのワンシーンをあるチャプターとして、時間方向にではなく縦割りにシーンで切り取って、シーン同士のつながりを書くように処理を定義していけるのがすごくいいです。
IO worldを返す関数が結構ありますが、実際には関数内で乱数でも使わない限りIOにはならないので、かなり純粋な気持ちで書けます。
最後にpureで返すだけです。
めちゃくちゃ気持ちいい。
パターンマッチで更に美しく
更に、この「ゲームの情報」のデータ型をうまく作ってやることで、do構文を全然使わなくてよくなり、かなり幸せになれます。
data KeyInput = IUpR | IDwR | IUpG | IDwG | IUpB | IDwB | INone
changeColor :: KeyInput -> Info -> IO Info
changeColor IUpR p@Info{..} = pure $ if _red == 255 then p else p{ _red = _red + 1 }
changeColor IDwR p@Info{..} = pure $ if _red == 0 then p else p{ _red = _red - 1 }
changeColor IUpG p@Info{..} = pure $ if _green == 255 then p else p{ _green = _green + 1 }
changeColor IDwG p@Info{..} = pure $ if _green == 0 then p else p{ _green = _green - 1 }
changeColor IUpB p@Info{..} = pure $ if _blue == 255 then p else p{ _blue = _blue + 1 }
changeColor IDwB p@Info{..} = pure $ if _blue == 0 then p else p{ _blue = _blue - 1 }
changeColor _ p = pure $ p
- 言語拡張の
RecordWildCards
を使っています。
んー、これももっときれいに書けたらいいんだけどなー。
Haskellのススメ
というわけで、Haskellをやったことがない方は、こんな楽しいパズルゲームみたいな人間本位の抽象化を楽しめるHaskellでどんどん遊びましょう。
機械の仕事を考えるのも楽しいですが、機械の仕事は機械の仕事!というまた別の高レイヤ視点でプログラミングを楽しめると思います。
というわけで、ぜひHaskellをやりましょう。
以上です。
明日の記事
><;;;;;!!!!!!