2016-11-14 4 views
3

何かのためにDSumを使用したいと思います。 DSumを使用するには、1つの型引数をとる 'タグ'型が必要です。このデータ型を他のデータ型からプログラムでどのように生成できますか?

data Tag a where 
    AFirst :: Tag Int 
    ASecond :: Tag String 

しかし、これをライブラリで内部的に使用したいと思います。私はユーザーに古いデータ型を取ることを暴露するインターフェースを望んでいます。

data SomeUserType1 = Foo Int | Bar String 

これは、上記のTag aタイプに移行するのは明らかにかなり機械的です。ですから、何らかの汎用プログラミング技術を使ってコードでこれを行うことは可能でしょうか?

ここでは、作成したいマッピングのタイプについてもう1つの例を示します。

data SomeUserType2 = Foo Int | Bar Char | Baz Bool String 

data Tag2 a where 
    AFirst :: Tag2 Int 
    ASecond :: Tag2 Char 
    AThird :: Tag2 (Bool, String) 

になるべきこのテンプレートHaskellのための仕事ですか?他に何か?私は実際にオプションが何であるか本当に知りません。

+0

@JonPurdy私は強く反対です! THだけが宣言を作成できます。 'ジェネリックス'と 'データ'は、デフォルトのインスタンスを作るようなもののためにほとんど役に立ちます。 – Alec

+0

@Alec:D'oh!私は自分自身を見ているだけです。 –

答えて

4

テンプレートHaskellは、宣言を生成しようとしているので、あなたが望むものです。ここで働くものがあります。

{-# LANGUAGE TemplateHaskell #-} 

module Tag where 

import Language.Haskell.TH 

makeTag :: Name -> DecsQ 
makeTag name = do 
    -- Reify the data declaration to get the constructors. 
    -- Note we are forcing there to be no type variables... 
    (TyConI (DataD _ _ [] _ cons _)) <- reify name 

    pure [ DataD [] tagTyName [PlainTV (mkName "a")] Nothing (fmap tagCon cons) [] ] 
    where 
    -- Generate the name for the new tag GADT type constructor. 
    tagTyName :: Name 
    tagTyName = mkName ("Tag" ++ nameBase name) 

    -- Given a constructor, construct the corresponding constructor for the GADT. 
    tagCon :: Con -> Con 
    tagCon (NormalC conName args) = 
    let tys = fmap snd args 
     tagType = foldl AppT (TupleT (length tys)) tys 
    in GadtC [mkName ("Tag" ++ nameBase conName)] [] 
      (AppT (ConT tagTyName) tagType) 

その後、あなたは別のファイルでそれをテストすることができます:あなたはGHCiの第二のファイルを検査し(または渡すことによって生成されたコードを見ると

{-# LANGUAGE TemplateHaskell, GADTs #-} 

import Tag 

data SomeUserType1 = Foo Int | Bar String 
data SomeUserType2 = Fooo Int | Baar Char | Baaz Bool String 

makeTag ''SomeUserType1 
makeTag ''SomeUserType2 

Tag.hsと呼ばれる一つのファイルに次のように入れて-ddump-splicesghciまたはghc)のいずれかに次が生成されることがわかります:

data TagSomeUserType1 a where 
    TagFoo :: TagSomeUserType1 Int 
    TagBar :: TagSomeUserType1 String 

data TagSomeUserType3 a where 
    TagFooo :: TagSomeUserType2 Int 
    TagBaar :: TagSomeUserType2 Char 
    TagBaaz :: TagSomeUserType2 (Bool, String) 

を私はを使用する必要がありますとではなくnewNameです。これらの生成されたGADTを使用することが期待される場合は、記述可能な名前を持つ必要があります。例から明らかなように、私の慣例では、型コンストラクタとデータコンストラクタの両方にTagを追加します。

+0

これは素晴らしいです。フォローとして、 'マジックSomeUserType1 == TagSomeUserType1' A'データ型 'データマジックtを定義することは可能でしょうか?それが遠隔で可能な場合、私はそうする方法について別の質問を掲示するかもしれません。 – ajp

+0

@ajpそれは可能だと思います。質問を更新するためのケア? (あなたが望むように、新しいものを作る) – Alec

+0

新しい質問はこちらhttp://stackoverflow.com/questions/40593441/how-can-i-produce-a-tag-type-for-any-datatype-for- use-with-dsum-without-templat – ajp

関連する問題