pigworkerは、質問のコメントで述べたよう私たちは私たちのタイプで、再帰についての事前情報を持っていない、と私たちは手動でチェックすることにより、再帰的な発生を掘るする必要があるため、デフォルトを使用してGeneric
表現は、偉大な醜さにつながります型の平等のために。ここでは、f-algebra-styleの再帰的な明示的な代替解を提示したいと思います。このためには代替の汎用Rep
が必要です。残念ながら、これは容易にGHC.Generics
に入ることができないことを意味しますが、それでもそれが啓発されることを願っています。
私の最初の解決策では、現在のGHC機能の中でできるだけシンプルなプレゼンテーションを目指しています。 2つ目の解決策は、より洗練されたタイプの、重いGHC 8ベースのものであるTypeApplication
です。
いつものように出始め:
{-# language
TypeOperators, DataKinds, PolyKinds,
RankNTypes, EmptyCase, ScopedTypeVariables,
DeriveFunctor, StandaloneDeriving, GADTs,
TypeFamilies, FlexibleContexts, FlexibleInstances #-}
私の一般的な表現は、積和の不動点です。 generics-sop
の基本モデルをわずかに拡張していますが、これも製品の合計ですが、ファンクションではないため、再帰的なアルゴリズムには不備があります。私はSOPが全体的に任意のネストされた型よりはるかに実用的な表現だと思う。なぜこれがpaperの場合に該当するかについての拡張引数を見つけることができます。要するに、SOPは不要なネスト情報を削除し、基本データからメタデータを分離することができます。
しかし、何よりも前に、ジェネリック型のコードを決定する必要があります。バニラのGHC.Generics
では、総和、型などの型コンストラクタがアドホック型レベルの文法を形成するため、明確な種類のコードはなく、型クラスを使用してそれらをディスパッチすることができます。私たちは、依存型ジェネリックの通常のプレゼンテーションにもっと忠実に従い、明示的なコード、解釈、関数を使用します。我々のコードは、種類のものでなければならない:
[[Maybe *]]
外側のリストには、各内側[Maybe *]
コンストラクタをコードする、コンストラクタの和を符号化します。 Just *
はコンストラクタフィールドに過ぎず、Nothing
は再帰フィールドを表します。たとえば、[Int]
のコードは['[], [Just Int, Nothing]]
です。NP
は再帰と非再帰的なフィールドのさまざまなコンストラクタを持っていることを
type Rep a = Fix (SOP (Code a))
class Generic a where
type Code a :: [[Maybe *]]
to :: a -> Rep a
from :: Rep a -> a
data NP (ts :: [Maybe *]) (k :: *) where
Nil :: NP '[] k
(:>) :: t -> NP ts k -> NP (Just t ': ts) k
Rec :: k -> NP ts k -> NP (Nothing ': ts) k
infixr 5 :>
data SOP (code :: [[Maybe *]]) (k :: *) where
Z :: NP ts k -> SOP (ts ': code) k
S :: SOP code k -> SOP (ts ': code) k
は注意してください。コードが型インデックスに明確に反映されるようにするため、これは非常に重要です。言い換えれば、NP
は[Maybe *]
のシングルトンとしても機能したいと考えています(しかし、良い理由から私たちは*
にパラメトリックになっています)。
定義内にk
パラメータを使用して、再帰のための穴を残します。
deriving instance Functor (SOP code)
deriving instance Functor (NP code)
newtype Fix f = In {out :: f (Fix f)}
cata :: Functor f => (f a -> a) -> Fix f -> a
cata phi = go where go = phi . fmap go . out
我々は2種類のファミリがあります:私たちは、GHCにFunctor
インスタンスを残して、いつものように再帰を設定
type family CurryNP (ts :: [Maybe *]) (r :: *) :: * where
CurryNP '[] r = r
CurryNP (Just t ': ts) r = t -> CurryNP ts r
CurryNP (Nothing ': ts) r = r -> CurryNP ts r
type family Alg (code :: [[Maybe *]]) (r :: *) :: * where
Alg '[] r =()
Alg (ts ': tss) r = (CurryNP ts r, Alg tss r)
CurryNP ts r
カレーNP ts
結果タイプr
とし、それはまたにr
にプラグ再帰的な出現。
Alg code r
は、SOP code r
に代数の型を計算します。個々のコンストラクタの除去器を組み立てます。ここでは単純なネストされたタプルを使用しますが、もちろんHList
-sでも十分でしょう。またNP
をHList
として再利用することもできましたが、あまりにもクルージングな感じがします。
すべてのことが残っていますが、機能を実装することです:
uncurryNP :: CurryNP ts a -> NP ts a -> a
uncurryNP f Nil = f
uncurryNP f (x :> xs) = uncurryNP (f x) xs
uncurryNP f (Rec k xs) = uncurryNP (f k) xs
algSOP :: Alg code a -> SOP code a -> a
algSOP fs (Z np) = uncurryNP (fst fs) np
algSOP fs (S sop) = algSOP (snd fs) sop
gcata :: Generic a => Alg (Code a) r -> a -> r
gcata f = cata (algSOP f) . to
それはその可能な形式であるため、ここでのキーポイントは、私たちが「正しい」SOP code a -> a
代数にAlg
でカリーエリミネーターを変換しなければならないことですcata
に直接使用してください。
のは、いくつかの砂糖とインスタンスを定義してみましょう:
(<:) :: a -> b -> (a, b)
(<:) = (,)
infixr 5 <:
instance Generic (Fix (SOP code)) where
type Code (Fix (SOP code)) = code
to = id
from = id
instance Generic [a] where
type Code [a] = ['[], [Just a, Nothing]]
to = foldr (\x xs -> In (S (Z (x :> Rec xs Nil)))) (In (Z Nil))
from = gcata ([] <: (:) <:()) -- note the use of "Generic (Rep [a])"
例:
> gcata (0 <: (+) <:()) [0..10]
55
Full code.
我々はカリー化していたし、持っていなかった場合は、それがよりよいだろう削除ツールを格納するためにHList
-sまたはタプルを使用する。最も便利な方法は、foldr
またはmaybe
のように、標準ライブラリの折り畳みと同じ引数の順序を持つことです。この場合、戻りタイプはgcata
で、タイプの汎用コードから計算するタイプファミリーによって与えられます。
type family CurryNP (ts :: [Maybe *]) (r :: *) :: * where
CurryNP '[] r = r
CurryNP (Just t ': ts) r = t -> CurryNP ts r
CurryNP (Nothing ': ts) r = r -> CurryNP ts r
type family Fold' code a r where
Fold' '[] a r = r
Fold' (ts ': tss) a r = CurryNP ts a -> Fold' tss a r
type Fold a r = Fold' (Code a) r (a -> r)
gcata :: forall a r. Generic a => Fold a r
このgcata
は非常に(完全に)曖昧です。私たちは明示的な申請かProxy
が必要です。私は前者を選択し、GHC 8の依存を受けました。しかし、一度我々はa
タイプを供給する、結果の型は減少し、我々は簡単にカレーができる:私は[_]
における部分型署名の上に使用
> :t gcata @[_]
gcata @[_] :: Generic [t] => r -> (t -> r -> r) -> [t] -> r
> :t gcata @[_] 0
gcata @[_] 0 :: Num t1 => (t -> t1 -> t1) -> [t] -> t1
> gcata @[_] 0 (+) [0..10]
55
。gcata1 @[]
として使用することができます
gcata1 :: forall f a r. Generic (f a) => Fold (f a) r
gcata1 = gcata @(f a) @r
:我々はまた、このための速記を作成することができます。
ここではimplementation of the above gcata
を詳しく説明しません。それは単純なバージョンよりはるかに長くはありませんが、gcata
実装はかなり毛深いです(恥ずかしがらず、私の遅れた回答を担当しています)。今私はそれを非常にうまく説明できませんでした。なぜなら、私はAgdaの助けを借りてそれを書いたからです。それは、たくさんの自動検索とタイプのテトリスを必要とします。
ジェネリックではこれを行うことはできませんが、このためのテンプレートのhaskellは簡単です。 – sclv
@sclv、なぜジェネリックではできないのですか? – dfeuer
データ構造の形状によって決定される_type_を持つ関数を生成したいからです。ジェネリックスを使用すると、基本的に「タイプに1つの穴」があります。これは、操作しているGenericのインスタンスを持つデータ構造のタイプです。 @sclv、右。 – sclv