問題
PureScript でゲーム エンジンを作成しようとしています。私はそれに慣れていませんが、以前に実世界の Haskell を経験したことがあるので、習得はスムーズでした (ただし、Haskell を「実際の」ものに使用した経験はあまりありません)。可能な限り多くの実行時エラーをコンパイル時エラーに変換することは、私の本では勝利ですが、言語が問題を抽象化する私の能力を過度に制限していることが判明した場合、その勝利の一部を取り除くことができます。
さて、私は HTML5 Canvas/context2d を介して PureScript で 2D ゲーム エンジンを構築しようとしています (明らかに、purescript-canvas はこれに最適な選択です - Elm の Graphics.Canvas モジュールよりもはるかに好きです。実際の基礎となる JS API により近くなり、特にキャンバスの個々のピクセルにアクセスできるようになります)。
私の既存の (未完成だが使用可能な) JS エンジンのコア機能は、「スプライト」のリストを保持し (それらはすべて共通のクラスを共有することを除いて異種)、それらすべてをループしてメソッドを呼び出す.update(timeDelta)
こと.draw(context2d)
でした。
スプライトはすべて共通のインターフェースを共有していますが、内部で根本的に異なるデータをサポートする必要があります。x/y 座標があるかもしれません。別の (おそらく環境効果を表す) には、「完了率」またはその他のアニメーション状態がある場合があります。
問題は、FFIを悪用して非常に不純なコードに侵入することなく、必要なことを行う同等の抽象化(異種/共有クラスリストへの)を思いつくことができないということです。
ソリューション (およびその問題)
異種リスト (duh)
明らかに、異種リストと同等のことを行うことができる最善の抽象化は、異種リストです。
Haskell スタイル
Haskell (つまり、公式の仕様/レポートではなく、だまされた GHC) がまさに私が望むものを提供していることがわかりました。クラスの制約を維持しながら、型情報をボックス化して、リスト内のすべての項目に単一のポリモーフィック関数を適用できます。型安全性を壊すことなく。これは理想的ですが、残念ながら PureScript では現在、次のような型を表現することはできません。
data ShowBox = forall s. Show s => SB s
PureScript スタイル (最先端)
PureScript の場合、purescript-existsパッケージがあります。これはおそらく、すぐ上の Haskell ソリューションと同等の機能を提供することを目的としており、型情報を非表示にするのではなく、削除して、元に戻すことができます。これにより、異種のリストを作成できますが、型の安全性が完全に失われます。
もっと言えば、私はそれを満足のいくように機能させることができないと思います[Exists f]
.forall a. (Draw a) => a
タイプ私は元に戻します。抽出する必要がある「実際の」種類のタイプを示す何らかの「タグ」を含めることもできますが、そのような種類の悪ふざけを引っ張っている場合は、プレーンな JS でコーディングすることもできます。私がしなければならないことがあります(リストについては、スプライトが含まれているとは限りません)。
すべての状態が 1 つの大量のデータ値に
個々のスプライトのすべての状態を 1 つの大規模な構造で表し、それを各スプライトの「更新」実装に渡すことにより、すべてのスプライトを同じタイプを持つものとして統合できます (まだクラス ポリモーフィズムを使用できませんが、突然変異を含めることができます)。タイプの一部として個々のスプライト値の関数を作成し、それを使用します)。これには明らかな理由があります。各スプライトには、他のスプライトのデータを変更/更新する自由があります。大規模なデータ構造は、表現する必要がある新しい種類のスプライトの状態ごとにグローバルに更新する必要があります。エンジンを使用するすべての人がエンジンを変更する必要があるため、ライブラリを作成できません。JSでも構いません。
分離均質型
または、各スプライトが個別の状態を持ち、すべてが同じ状態表現を持つこともできます。これにより、「お互いのパイに指が入る」というシナリオは回避できますが、すべてのスプライトのニーズに関する過剰な知識、不要なタイプ構造のビットの大量の無駄なデータで更新する必要がある均一な構造がまだあります。すべてのスプライトによって。非常に貧弱な抽象化。
JSON でさまざまなデータを表すか、何を持っているか
ええ。この方法は、基本的にJS データを使用し、PureScript のふりをしているだけです。PureScript のタイピングのすべての利点を捨てなければなりません。
抽象化なし
それらすべてをまったく無関係なタイプとして扱うことができました。これは、新しいスプライトを追加したい場合は、最も外側の関数を更新して を追加する必要があることを意味しdraw
ます。おそらく考えられるすべての解決策の中で最悪です。drawThisParticularSprite
update
私がおそらくすること
利用可能な抽象化の選択肢の評価が正しいと仮定すると、必要なことを行うにはFFIを何らかの方法で悪用する必要があることは明らかです。おそらく、次のような統一されたレコードタイプがあります
type Sprite = { data: Data, draw: Data -> DrawEffect, update: Data -> Data }
Data
たぶんExists f
ある種のような、型抜きされた厄介なものはどこにありますか?
type DrawEffect = forall e. Eff (canvas :: Canvas | e) Context2D
か何か。draw
メソッドとメソッドはどちらも個々のupdate
レコードに固有であり、どちらも から抽出する真のタイプを「認識」していますData
。
それまでの間、PureScript の開発者たちに Haskell スタイルの実存的なものをサポートする可能性について尋ねて、型の安全性を壊すことなく適切で真の異種リストを取得できるようにしたいと思います。主なビットは、(以前にリンクされたHaskellの例の場合)、ShowBox
(非表示の)メンバーのインスタンス情報を保存する必要があるため、関数Show
の独自のオーバーライドから、使用する正しいインスタンスを知ることになると思いますshow
.
嘆願
PureScript で現在利用可能なオプションに関して、上記が正確かどうか誰か確認してもらえますか? 特に、この問題に対処するためのより良い方法を見つけた場合、特に抽象化を犠牲にすることなく「純粋な」コードのみを使用できるようにする方法がある場合は、お知らせください。