8

私は特定のデータ構造の2つの実装の間で引き裂かれ、何が正しい/標準であるかについてHaskellコミュニティからの入力をいただければ幸いです。

データ型

たとえば、複数のサーバーをnullaryデータコンストラクターとして定義するADT「サーバー」を考えてみましょう。

data Server = Server1
            | Server2
            | Server3

さて、これらのサーバーのそれぞれについて、(とりわけ)IPアドレスを取得する機能が必要です。これらを静的にコーディングできると仮定すると、いくつかの関数「getURL」とパターンマッチを使用できます。

getUrl :: Server -> String
getUrl Server1 = "192.168.1.1"

これで、サーバーを使用するすべての関数で、サーバーをタイプに入れてgetURLを呼び出すことができます。

serverStuff :: Server -> IO ()

このメソッドには、getURLで多くのパターンマッチングが行われるという犠牲を払って、単純な非ポリモーフィック関数の利点があるようです。さらに、プログラマーがサーバーを追加したが、getURLにパターンを追加するのを忘れた場合、-Wallでコンパイルしない限り、警告なしにランタイムエラーが発生します。

型クラス

タイプクラスで同じ問題を攻撃して、マルチコンストラクターADTをサーバーに固有のADTのセットに分割し、URLのタイプクラスを作成できます。

data Server1 = Server1
data Server2 = Server2
data Server3 = Server3

class Server a where
    getUrl :: a -> String

instance Server Server1 where
    getUrl Server1 = "192.168.1.1"

など。これで、以前使用した単純な非ポリモーフィック関数の代わりに、次のようなものを作成する必要があります。

serverStuff :: Server a => a -> IO ()

アドホック多相性(関数の特殊化など)の影響に対処します。

明るい面として、typeclassメソッドは拡張が簡単で、パターンマッチングをより小さなチャンクに分割し、グループ化されたサーバー(data ServerCenter1 = Server1 | Server2 | Server3)などのより高度な抽象化を可能にします。 getUrlを宣言すると、インスタンスを作成するときに少なくともその決定を行う必要があります。

だから、私は引き裂かれていますが、物事を行うためのより良い方法としてインスタンスに傾いています。この問題を処理するための標準的な方法はありますか、それとも「クリーンに見えるものは何でも」タイプのものですか?

4

1 に答える 1

13

サーバータイプに含める必要がある唯一の情報であることに自信がある場合は、それらを文字列の周りに newtype として実装します。

newtype Server = Server { getURL :: String }

(hammar のコメントのように) 完全なレコードにすることで、GeneralizedNewtypeDeriving を犠牲にして、コンストラクターのみを変更しながら情報を追加できます。

一般に、型を使用して物事のクラスを表し、変数を使用して詳細を表すので、nullary コンストラクターは抽象data Status = Published | Draft(または組み込みの Bool) を表すためだけに使用されます。特定の理由がない限り、データ (IP アドレスなど) を型システムまたは関数にハードコーディングすることは避ける必要があります。


サーバー固有の動作が必要な場合は、レコードにフィールドを簡単に追加できます。

data Server = Server {
              getURL      :: String
            , doSomething :: a -> IO () --Or any other functional signature
            }

ただし、他のコードがわかりにくくなるため、これを行うことはお勧めしません。

runSomething :: Server -> a -> IO ()
runSomething server arg = (doSomething server) arg

絶対に何でもできるので、そのフィールドの最後の更新を見つけて、何を決定する必要があります (関数にはShowインスタンスがないため)。依存関係がサーバーのプロパティに依存している場合、そのプロパティをエンコードしてからディスパッチすることをお勧めします。

data ServerType = Production
                | Development

data Server = Server {
              getURL     :: String
            , serverType :: ServerType
            }

runSomething :: Server -> a -> IO ()
runSomething server arg = case serverType server of
                              Production  -> foo arg
                              Development -> bar arg

このアプローチは、サーバー名を関数にハードコーディングするよりも優れていると考えています。これは、特定のサーバーが特定の動作をする理由を説明し (そして、特定のサーバーの動作をよりローカルに変更できるようにするため)、レコード フィールドに関数を含めるよりも優れていると考えています。特定の runSomething の呼び出しが何を行うかを簡単に判断できます (ServerType を調べてログに記録できるため)。

于 2012-11-28T17:16:00.547 に答える