5

そのため、代数型と型クラスをよく理解していますが、そのソフトウェア エンジニアリング/ベスト プラクティスの側面に興味があります。

型クラスに関する現代のコンセンサスがあるとすれば、それは何ですか? 彼らは悪ですか?彼らは便利ですか?それらはいつ使用されるべきですか?

これが私のケーススタディです。私はRTSスタイルのゲームを書いていて、さまざまな種類の「ユニット」(タンク、スカウトなど)を持っています。各ユニットの最大ヘルスを取得したいとします。それらのタイプを定義する方法に関する私の2つの考えは次のとおりです。

ADT のさまざまなコンストラクター:

data Unit = Scout ... | Tank ...

maxHealth :: Unit -> Int

maxHealth Scout  = 10

maxHealth Tank = 20

Unit の型クラス、各種類はインスタンス

class Unit a where
    maxHealth :: a -> Int

instance Unit Scout where
    maxHealth scout = 10

instance Unit Tank where
    maxHealth tank = 20

明らかに、最終製品にはさらに多くのフィールドと機能が含まれます。(たとえば、各ユニットの位置などは異なるため、すべての機能が一定であるとは限りません)。

秘訣は、一部のユニットには意味のある関数があり、他のユニットには意味がないということです。たとえば、すべてのユニットには getPosition 関数がありますが、戦車には getArmour 関数がある可能性があります。これは、装甲のないスカウトには意味がありません。

他の Haskeller が私のコードを理解して従うことができるようにしたい場合、これを書く「一般的に受け入れられている」方法はどれですか?

4

3 に答える 3

7

ほとんどの Haskell プログラマーは、不必要な型クラスに眉をひそめます。これらはタイプの推論を傷つけます。Unitトリックなしでは s のリストを作成することさえできません。GHC では、すべての秘密の辞書が渡されます。彼らはどういうわけかハドックを読みにくくします。それらはもろい階層につながる可能性があります...おそらく他の人があなたにさらなる理由を与えることができます. 避けるほうがずっと苦痛なときに使うのが良いルールだと思います。たとえば、 がなけれEqば、関数を手動で渡して、たとえば 2 つ[[[Int]]]の を比較する (またはアドホック ランタイム テストを使用する) 必要があり、これは ML プログラミングの問題点の 1 つです。

このブログ投稿をご覧ください。合計タイプを使用する最初の方法は問題ありませんが、ユーザーが新しいユニットなどでゲームを変更できるようにしたい場合は、次のような方法をお勧めします

data Unit = Unit { name :: String, maxHealth :: Int }

scout, tank :: Unit
scout = Unit { name = "scout", maxHealth = 10 }
tank  = Unit { name = "tank",  maxHealth = 20 }

allUnits = [ scout
           , tank
           , Unit { name = "another unit", maxHealth = 5 }
           ]

あなたの例では、戦車には装甲がありますが、スカウトにはありません。明白な可能性は、フィールドや特別な力のリストなどの追加情報でユニットタイプを強化することMaybe Armorです...必ずしも決定的な方法があるとは限りません.

おそらく過剰な解決策の 1 つは、拡張可能なレコードを提供する Vinyl のようなライブラリを使用して、サブタイプの形式を提供することです。

于 2013-07-25T04:53:46.127 に答える
3

私は、手動でインスタンスを生成して渡すのが面倒な場合にのみ、型クラスを使用する傾向があります。私が書いたコードでは、これはほとんどありません。

于 2013-07-25T07:19:39.543 に答える
2

型クラスを使用する決定的な時期についての回答を検討するつもりはありませんが、現在、Unit クラスについて説明した両方のメソッドを使用するライブラリを作成しています。私は通常 sum型に頼っていますが、typeclass メソッドには大きな利点が 1 つありますUnit

Unitこれにより、 s に対してポリモーフィックである必要がある関数は、最終的には抽象型クラスに基づいて定義された関数のみを使用する必要があるため、インターフェイスにもう少し書く必要があります。Unit私の場合、ファントム型の型パラメーターとして -type 型を使用できることも重要です。

たとえば、Nanomsg (ZMQ の元の作成者による ZMQ に似たプロジェクト) への Haskell バインディングを作成しています。Nanomsg にはSocket、表現とセマンティクスを共有する型があります。各Sockets には正確に 1 つがあり、一部の関数は特定の の s でProtocolのみ呼び出すことができます。これらの関数でエラーをスローしたりs を返したりすることもできましたが、代わりにs をクラスを共有する個別の型として定義しました。SocketProtocolMaybeProtocol

class Protocol p where ...

data Protocol1 = Protocol1
data Protocol2 = Protocol2

instance Protocol Protocol1 where ...
instance Protocol Protocol2 where ...

およびSockets にファントム型パラメーターを持たせる

newtype Socket p = Socket ...

これで、間違ったプロトコルで関数を呼び出すことを型エラーにすることができます。

funOnProto1 :: Socket Protocol1 -> ...

一方、Socket単なる合計型の場合、これをコンパイル時にチェックすることは不可能です。

于 2013-07-25T05:38:14.370 に答える