8

私が取り組んでいる HLearn ライブラリには、次のようなコンテナー データ型があります。

data (Model params model) => Container' params model = Container'
    { baseparams :: params
    , basemodel  :: model
    }

params問題は、とmodelが互いに一意に決定されるため、この型は使いにくいということです。

class Model params model | params -> model, model -> params

したがって、型を指定するときに両方を指定する必要がなければ、はるかに便利です。コンパイラは自動的にそれを行うことができるはずです。

この問題を解決するための私のアイデアは、存在量化を使用する型エイリアスを作成することでした:

type Container model = forall params . (Model params model) => Container' params model

しかし、これはうまくいきません。通常のようにインスタンスを作成するとContainer'、すべて正常に動作します。

data ContainerParams params = ContainerParams params

instance (Model params model) => Model (ContainerParams params) (Container' params model)

しかし、私のContainerタイプを使用すると:

instance (Model params model) => Model (ContainerParams params) (Container model)

ghc が爆発する:

不正なポリモーフィックまたは修飾された型: コンテナー モデル `Model (ContainerParams params) (Container model)' のインスタンス宣言内

このエラー メッセージの意味がわかりません。Containerパラメータを指定する必要のない型を作成するために、私のソリューションを何らかの形で修正することは可能ですか?


forall編集:ステートメントをContainer'宣言に移動するには、たくさんのunsafeCoerces が必要なように思われるので、それは悪い解決策のように思われることに注意してください。

また、 を変更して機能type Containerさせることもできますdata Containerが、これには一部であるすべてのインスタンスを再宣言する必要がConatiner'あり、そうしたくありません。私はこのパターンに従うさまざまなタイプを持っているので、この問題を解決する一般的な方法があるはずです。

4

1 に答える 1

12

普遍的な定量化が必要なのか、実存的な定量化が必要なのかわかりません。いずれにせよ、新鮮なタイプで包むのが一番です。

強くお勧めします: 通常のデータ型に制約ヘッドを使用しないでください。彼らはあなたの人生を楽にするのではなく、難しくします。彼らは何の役にも立ちません。

実存的

{-# LANGUAGE GADTs #-}
data Container' params model = Container'
{ baseparams :: params
, basemodel  :: model
}
data Container p m where
  Container :: Model params model => Container' params model -> Container params model

ユニバーサル

{-# LANGUAGE Rank2Types #-}
data Container' params model = Container'
{ baseparams :: params
, basemodel  :: model
}
newtype Container model = Container (forall params . Model params model => Container' params model)

型クラス インスタンスにユニバーサル型または修飾型を含めることはできません。そう

instance Model (ContainerParams params) (Container model)

タイプシノニムが展開されるため、許可されていません

instance Model (ContainerParams params) (forall ...)

私の GADT ソリューションでは、 と の両方をパラメーターとして扱いましparammodel。これは、知っておくべき重要な理由によるものです: 機能的な依存関係はコンフルエントではありません! また、コンパイラは、型チェックの目的でそれらが合流しているとは想定していません。関数の依存関係は、制約ソルバーをガイドする場合にのみ役立ちます (プロローグの「カット」のような追加の論理構造に似ているという意味で)。合流が必要な場合は、 を使用しますTypeFamilies

class Model model where
  type Param model
  ...

またはそれを行う素晴らしい方法

class (model ~ (TheModel param),param ~ (TheParam model)) => Model model param where
    type TheModel param
    type TheParam model

これは、ファンデップと双方向で同じです。そして、これにより、存在するインスタンスを次のように記述できます

data Container model where
   Container :: Model model param => Container' model param -> Container model

そして、存在量化されたものが一致するmodelことがわかっているので、同じタイプの 2 つのコンテナーを結合するなどのことができます。paramsこれを使用して、定義できます

data HasParam model where
   HasParam :: Model model param => HasParam model

data GADTContainer model where
   GADTContainer :: Model model param => Container' model param -> GADTContainer model

newtype NewContainer model 
   = NewContainer (forall param. Model model param => Container' model param)

そして、タプルは、これらの型の間の関係も説明する証明可能な(HasParam model, NewContainer model)同形ですGADTContainer model

とにかく、それを処理したら、適切なラップされた型を使用してインスタンスを定義できます。

于 2013-01-22T03:50:54.943 に答える