@igrep)と、いうわけで先週私はHaskellでプリキュアを実装しました。typesafe-precureと言います。Haskellした紹介をさせてください。PreCure!!ご存知(?)、rubicureをはじめとするプリキュア実装のHaskell版です。他のプリキュア実装と異なり、名前の通り型安全であることに大きく重きをおいています。プリキュアだけでなく変身アイテムや必殺技を放つときのアイテム(「特殊アイテム」とここでは呼びます)を収録している点も、他のプリキュア実装と一線を画す特徴でしょう。初代「ふたりはプリキュア」でなぎさとほのかが変身するには、2人で手をつなぎつつ、それぞれのカードコミューンを使用する必要があります。以下が2人で変身する時の台詞を取得する場合のコードです。CardCommune_Mepple違う変身アイテムを使用して変身するなんてことはできません。LinkleStoneDia)型が合わない変身なんて、興味がありません!「ふたりはプリキュア!!」って2人で叫んでいるのに1人で変身できるとか、変じゃないですか!「光の使者、キュアブラック!」ってキュアブラックが言った後「光の使者、キュアホワイト!」ってキュアホワイトが言ってるのに片方の台詞しかないとか、変じゃないですか!それから、プリキュア全員が同時に行う必殺技(「浄化技」と呼ぶこともあります)についても、当然全員が揃わないと実行できないよう作られています。プリンセスプリキュア」の終盤で使用される必殺技、「プリキュア・グラン・プランタン」の台詞を取得する際のコードです。プリンセスプリキュア」の5人は「モードエレガント・ロイヤル」という特殊なフォームに変身していなければなりません。これらにも別の型を割り当てました。CureFlora""当然、「モードエレガント・ロイヤル」に変身する際には4人全員が揃い、専用のアイテムを使用する必要があります。RoyalDressUpKey)PreCureMonadHaskellでプリキュアを実装する上で外せない機能が、もう一つあります。Monadです!「タイプセーフプリキュア!」では、各種変身シーンや浄化技発動時の台詞をリストに組み立てるためのMonadを実装しました。プリンセスプリキュアがモードエレガント・ロイヤルに変身してから浄化技を発動するまでの台詞を組み合わせてみましょう。"]単に文字列のリストを組み立てるだけじゃ面白くないので、rubicureが実装していた機能にならい、セリフを一行ずつ表示する機能を実装しました。:}PreCureMonadはsyocyさんも「Haskellライブラリ所感2016」という記事で紹介されていたmonad-skeletonというライブラリーを使ってさくっと作りました。Monadをnewtypeするので十分な気はしますが。使ってみて面白かったのでよしとしましょう。使用しているGHCの拡張さて、ライブラリー自体の紹介はこの辺にして、ここからは開発の際に使用したGHCの拡張について解説しましょう。すべてメジャーなものだと思うので、少しでも参考にしていただければ幸いです。MultiParamTypeClasses「タイプセーフプリキュア!」では、一人一人の女の子、プリキュア、一つ一つのプリキュアの変身スタイル、変身アイテムそれぞれに固有の型を割り当てています。PreCureやSpecialItemなどといった型は存在していません。型の「組み合わせ」に対して性質を与える必要があります。そこで今回はMultiParamTypeClassesというGHCの拡張を使用しました。名前の通り、これは型クラスが複数の型パラメーターをとることができるようになる拡張です。以下は、浄化技を使用できるプリキュアと、その際に必要な特殊アイテムの組み合わせを表す型クラスです。[String]インスタンス宣言は次のようになります。]このようにMultiParamTypeClassesは、複数の型の組み合わせごとに、メソッドを実装しわけたい場合に使います。(この程度ならJavaオーバーロードとかで十分できるとかそういうこと言わない)オブジェクト指向の世界の用語で言えば、マルチディスパッチとちょっと似ています。MonadReaderなどの型クラスでしょう。Monadっぽく振る舞う型、全般を束ねる型クラスです。そのためMultiParamTypeClassesが使用されています。結果、例えばMonadState型クラスは以下のような定義となっています(説明のために大幅に簡略化しています)。2が、このように、型パラメーターを複数とる型を型クラスとして抽象化したいときにも使えます。FlexibileInstancesMultiParamTypeClassesのお陰で「型の組み合わせ」に対してメソッドを定義できるようになりました。これでプリキュアと変身アイテムの「組み合わせ」に対して個別の実装を割り当てることができるようになります。ところが、プリキュアの世界には更に複雑なケースが存在します。実はすでに紹介した例に登場しているのですがお気づきでしょうか...?そう、プリキュアは2人以上揃っていなければ変身できないことがあるのです!Haskellの仕様上、2人以上の女の子が必要だからといってtransformationSpeech関数やtransform関数の引数を増やすわけにも行きません。そこで「タイプセーフプリキュア!」では、2人以上の女の子やプリキュアが変身・浄化技に必要な場合は、タプルを使って表現することにしました。先ほどのなぎさとほのかの例が典型的ですね。CardCommune_Mipple)という型の組み合わせで型クラスのインスタンスを宣言するには、それらのタプルに対してインスタンス宣言をしなければなりません。これを可能にするためにはFlexibileInstancesが必要です。この拡張を使わない場合、型クラスは仕様上、タプルのような型パラメーターを受け取る型をインスタンスとして宣言する際、型パラメーターをすべて抽象化した状態で(単純な型引数として)適用しなければなりません。以下のようなインスタンス宣言は、FlexibileInstancesを使わなければエラーになります。型引数が両方とも具体的。2つ目の型引数が具体的。1つ目の要素の型が具体的。2つ目の要素の型が1つ目の型と同じでなければならない、なのでこれもダメ。b)なぜダメなんでしょう?例えば以下のような使い方ができてしまうからです。a)もどちらも当てはまってしまいます。このように、型クラスの解決が困難になってしまう場面が存在するため、現在のHaskell標準ではこうしたインスタンス宣言を禁止しています。この制限を緩和してくれるのがFlexibileInstancesです。a)のように曖昧になるインスタンス宣言が存在しない限り、型引数に具体的な型を当てはめたインスタンス宣言を認めてくれるようになります。詳しいルールについては結構複雑なのでGHCのドキュメントのこのへんをご覧ください。「型を受け取って型を返す関数」を使えるようにしてくれます。単純なものを例えるなら、「型と型を紐付ける連想配列」といった方がピンとくるでしょう。「タイプセーフプリキュア!」でもそのような使い方しかしていません。以下のようなHaskellの型をキーにしたJSON(みたいなもの)をイメージしてみてください。familyの文法で書くと次のようになります。下記の関数は[Double]を受け取ってCharを返す関数となる。familyは文字通り「型を受け取って型を返す関数」なのです。実は再帰呼出しをしたりもできるので、本当に関数のように使えます。familyについて、もっと頻繁に使われる使い方を示しましょう。familyも型クラスのメソッドとして宣言することができるのです。「タイプセーフプリキュア!」でもやはりそうした使い方をしています。さっきから何度も出ているtransformationSpeechを定義しているのはこの型クラスです。family名>と宣言するだけで定義できます。familyに該当する型も定義しなければならなくなります。Styleには、「変身した後のプリキュア、プリキュアの特殊フォーム(を表す型)」を定義しなければなりません。GoGo!」における「夢原のぞみはキュアモを使ってキュアドリームに変身する」という設定を表すインスタンス宣言は次のようになります。「変身した後のスタイル」を取得するための関数を定義するためには、Transformationのインスタンスごとに異なる型の値を返せるようにする必要があります。familyが必要になったのです。実践的な使用例せっかくなんでプリキュアよりももっともっとも〜っと実践的な使用例を示しましょう。個人的にぱっと思いついたのはmono-traversableです(もっといろいろな例があると思うので教えてほしい)。MonoTraversableを提供してくれます。どのように一般化したのでしょう?例えばByteStringのような、「コンテナーっぽいけれど、中に含んでいる要素が一種類しかない」型も抽象化できるようにしてくれるのです。Traversableのインスタンスとなる型は、いずれも型引数を1つとる型でなければいけません。取りうる要素を型ごとに指定することで、ByteStringやTextみたいな、要素として使える型が一種類しかないコンテナーもサポートします。familyのソースを一部見てみましょう。aのとおり、ByteStringやTextのような型に対してはWord8やCharといった決まった型を返し、リストやMaybeのように任意の型を要素として使える型に対しては要素の型をそのまま返すように作られています。結果、mapのmono-traversable版であるomapという関数は、次のような型となります。aだと判断するようになるのです。TemplateHaskellTemplateHaskellは強力なメタプログラミング機構です。Lensなどを使う際にも直接使うことがあるので、みなさんも他の拡張より親しみがあるかもしれません。[Dec]など、Haskellの構文木を表した型を返す関数を定義・使用することで、コンパイル時にHaskellのソースコードを自動生成します。「コンパイル時限定のLispのマクロのようなもの」と言えばピンとくる方もいらっしゃるかもしれません。似たような関数・型をたくさん宣言したい時に記述を簡略化したり、データベースのスキーマや設定ファイルといった、Haskellのソースコードの「外にあるもの」から自動で関数・型を定義したい時など、ユースケースはいろいろあります。Haskellで定義したマクロを使用して定義しました。そのため、先ほど出てきた「夢原のぞみはキュアモを使ってキュアドリームに変身する」という設定を表すインスタンスは、実際には次のように宣言されています。変身や浄化技・必殺技の台詞を募集します!前回のブログ記事にも書きましたが、ここでも募集させていただきます。正しさにこだわって実装したtypesafe-precureですが、残念ながら浄化技やプリキュアの特殊なフォームは一部しか実装されていません。時間の力には勝てませんでした。そのため、みなさんの好きなあの技・あのフォームが定義されていない可能性が十分にあります。した場合は、こちらのルールにしたがって報告していただけると幸いです!また、もちろん誤字・脱字や設定の大きな間違いについても受け付けます(キリがなくなりそうな状態になると私の一存でクローズするかもしれません。お気づきの場合は以下から報告してください。igrep/typesafe-precure追加したい機能とかGHCのいろいろな拡張を試すつもりで実装した「タイプセーフプリキュア!」でしたが、思いついたアイディアを試すもなかなかうまく行かず、思ったよりもシンプルな実装に落ち着いてしまったな、と思います。非互換な変更になってはしまいますが、気が向いたらがさくっと設計を変更するかもしれません。それからもっと大事なこととして、ここまで挙げたコード例から察せられるとおり、typesafe-precureは現状非常に冗長で、非実用的な実装になってしまっています。これでは使っていただけないでしょうし、収録していない台詞を集めていただくモチベーションも上がらないと思います。そこで収録しているプリキュアの情報をJSONなどに変換して出力する機能を検討しています。Haskellを使えば型クラスの定義から自動的に作れるはずです。rubicureがyamlで定義したプリキュアの情報をRubyから使えるようにしているのと、ちょうど逆のことをやろうとしているわけですね。してしまったようです。_)merやるっしゅ!厳密に言うと「2つの型パラメーターを受けとる型を型クラスで束ねる」すべてのケースでMultiParamTypeClassesが必要になるわけではないはずですが、ちょっと私はうまく説明できません...。MonadReaderはFunctionalDependenciesという別の拡張を併用して定義されているので、「型の組み合わせ」によってメソッドの実装を分けるといった使い方はできません。FunctionalDependenciesについては今回は使用していないので割愛します。Yamamoto@igrepHaskellとプリキュアが好き。眠り、よくコードを書きたい。upLoginユーザーは見つかりませんでした。