2016-10-12 8 views
4

メッセージキュー用のlibを作成しています。キューはDirectまたはTopicのいずれかです。 Directキューには静的バインディングキーがあり、Topicキューには動的バインディングキーを設定できます。タイプの1つのコンストラクタでしか機能しない関数

Directキューでのみ機能するpublishを書いてみたいと思います。これは動作します:

{-# LANGUAGE DataKinds #-} 

type Name = Text 
type DirectKey = Text 
type TopicKey = [Text] 

data QueueType 
    = Direct DirectKey 
    | Topic TopicKey 

data Queue (kind :: a -> QueueType) 
    = Queue Name QueueType 

これは、2つの個別のコンストラクタ

directQueue :: Name -> DirectKey -> Queue 'Direct 

topicQueue :: Name -> TopicKey -> Queue 'Topic 

が必要です。しかし、私は公開書くために行くとき、私は

publish :: Queue 'Direct -> IO() 
publish (Queue name (Direct key)) = 
    doSomething name key 
publish _ = 
    error "should be impossible to get here" 

不可能であるべきと一致する必要があり、余分なパターンがありますこのパターンマッチングを必要としないように、この問題をモデル化するより良い方法はありますか? Directキューは常にTextメタデータを持ち、Topicキューは常に[Text]メタデータを持つ必要があります。タイプと値の両方でこれを実施するより良い方法はありますか?

+0

は、あなたの代わりに2つの異なるタイプ 'のnewtype DirectQueue = DQ Text'と' newtypeのTopicQueue = TQ [テキスト]のパラメータ化 'Queue'タイプを、'必要な理由はありますか? – chepner

+0

@chepner - 彼らはお互いに共通点があります。私は問題を単純化するために余分な情報を取り出した。これをサンプルに戻してデモンストレーションします。 –

+0

キューの 'Name'に、共通の情報があることを示すために追加しました。実際のアプリケーションでは、どのキューでも機能するいくつかの関数があり、キューにはキューの種類に加えて2つの別個のフィールドがあります。 –

答えて

5

どうQueueプレーンポリモーフィック型の

data Queue a = Queue Name a 

を作る。そして、別Queue DirectKeyQueue TopicKeyタイプの定義は?その後、publish :: Queue DirectKey -> IO()でパターンマッチする必要はありません。

、それとは別に、あなたはおそらく、あなたがDirectKeyTopicKeyはインスタンスになりその型クラスでは、いくつかの一般的な操作を定義し、次に

commonFunction :: MyTypeclass a => Queue a -> IO() 
のようなシグネチャを持つことができ、任意の Queueで動作するはずの機能が必要な場合

たぶん、あなたはそのままあなたのコードがコンパイルされません(それは同様オンにするPolyKindsが必要)ので、私は自演していない

class MyTypeclass a where 
    commonFunction :: Queue a -> IO() 
+0

これは素晴らしいです。私は間違いなくそれを思っていた。ありがとう! –

+0

これは、タイプパラメータを別のタイプに追加することがどのように役立つかについての良い話です。https://www.youtube.com/watch?v=BHjIl81HgfE – danidiaz

2

型クラスで直接このような機能を入れることができますwそれは偶発的なものだけど、コンストラクタが関与する可能性のあるキューの型から知っているアプローチのためにしようとしていたように見えるので、ある種の関数しか呼び出せないことを静的に保証することができますキューの。

GADTの複数のコンストラクタを使用して実際にそのアプローチを得ることができます(複数の完全に別々の型を使用するのではなく、必要なときに型クラスを使用して@danidiazの答え)。

しかし、なぜあなたの現在のコードが機能しないのですか?キュー・タイプの場合:

data Queue (kind :: a -> QueueType) 
    = Queue Name QueueType 

あなたはあなたがそれになりたいQueueTypeの種類によってタイプレベルでQueueをタグ付けすることができ、(kindと呼ばれる)型変数でQueueタイプをパラメータ化しています。しかし、コンストラクタQueue Name QueueTypekindをまったく参照しません。それはファントムタイプです。そのQueueTypeスロットは、キューのタイプがQueue kindkindに関係なく、有効なキュータイプで埋められます。

これは、Queue 'Direct内のトピックキーと一致するケースをpublishに追加したい場合、GHCが正しいことを意味します。そのデータ型定義には、そのような値が存在する可能性があります。

GADTでできることは、戻り値の型がであるを別々に明示的に宣言することです。したがって、構築している値の型と、その型の値を作るために使用できるコンストラクタ(またはそのパラメータ)の間の関係を設定できます。具体的には

、我々はQueue 'Directのみが直接キュー・タイプを含むことができ、かつQueue 'Topicのみが話題キュータイプを含めることができ、あなたは多形受け入れることのいずれかによって扱うことができるようにあなたのキューの種類を作ることができますQueue a

それはQueueTypeだけタグに使用することを行い、データを格納する別のGADTを持つことが最も簡単です。元のコードでは、データ保持コンストラクタをタイプレベルに持ち上げて適用せずに再利用することができましたが、それはあなたの種類の署名を不必要に複雑にします(PolyKindsを必要とします)。さらに追加する必要がある場合!)パラメータをデータコンストラクタに追加すると、適用されない型を型レベルに持ち上げたときに同じ種類に適合させることがますます難しくなります。だから、:

data QueueType 
    = Direct 
    | Topic 

data QueueData (a :: QueueType) 
    where DirectData :: DirectKey -> QueueData 'Direct 
     TopicData :: TopicKey -> QueueData 'Topic 

だから我々は単に(これまでに実際に値レベルで、このようなタイプを使用する必要がしばしばありません)DataKindsを持ち上げるためにQueueTypeを持っています。次にタイプタイプQueueTypeによってパラメータ化されたタイプQueueDataが得られました。一方のコンストラクタはDirectKeyをとり、QueueData 'Directを構成し、もう一方のコンストラクタはTopicKeyをとり、QueueData 'Topicを構成します。それは名前だけにアクセスする必要があるためと言う(関数がどのキューに動作するかどう

data Queue (a :: QueueType) 
    = Queue Name (QueueData a) 

そして、それは同様にキューのタイプが表されている でタグ付けされたQueueタイプを持つことは簡単ですQueueData)の外に、それはQueue aを取ることができます。

getName :: Queue a -> Text 
getName (Queue name _) = name 

明示的にすべてのケースを扱うことができる場合にもQueue aを取ることができ、そしてときにyあなたが警告を取得しますOUケース欠場:

getKeyText :: Queue a -> Text 
getKeyText (Queue _ (DirectData key)) = key 
getKeyText (Queue _ (TopicData keys)) = mconcat keys 

をそして、あなたのpublish機能のようにQueue 'Directを取ったときに最後に、GHCはDirectDataQueueDataための唯一の可能なコンストラクタであることを知っています。したがって、OPのようにエラーケースを追加する必要はなく、内部にTopicDataを処理しようとすると、実際にはタイプエラーとして検出されます。

全例:

{-# LANGUAGE DataKinds, GADTs, KindSignatures #-} 

import Data.Text (Text) 

type Name = Text 
type DirectKey = Text 
type TopicKey = [Text] 

data QueueType 
    = Direct 
    | Topic 

data QueueData (a :: QueueType) 
    where DirectData :: DirectKey -> QueueData 'Direct 
     TopicData :: TopicKey -> QueueData 'Topic 

data Queue (a :: QueueType) 
    = Queue Name (QueueData a) 


getName :: Queue a -> Text 
getName (Queue name _) = name 

getKeyText :: Queue a -> Text 
getKeyText (Queue _ (DirectData key)) = key 
getKeyText (Queue _ (TopicData keys)) = mconcat keys 

publish :: Queue 'Direct -> IO() 
publish (Queue name (DirectData key)) 
    = doSomething name key 
    where doSomething = undefined 
+0

「PolyKinds」はむしろ必要以上のものだと思います。 'KindSignatures'で十分でしょう。 – dfeuer

+0

@dfeuer書かれたOPのコードはkind :: a - > QueueTypeのような種類の変数aを持ちます。私のシステム(ghc 8.0.1)は、 'KindSignatures'と' PolyKinds'ではそれを受け入れません。 – Ben

+0

ええと...ええ。あなたは絶対に正しいです。 – dfeuer

関連する問題