2013-07-06 8 views
10

私は最近F-algebrasについて少し学んだ: https://www.fpcomplete.com/user/bartosz/understanding-algebras。 私は、この機能をより高度な種類(索引付きおよび高級品)にしたいと考えていました。 さらに、 "Haskell a Promotion"(http://research.microsoft.com/en-us/people/dimitris/fc-kind-poly.pdf)をチェックしました。これは、自分の漠然とした "発明"に名前を付けたので非常に役に立ちました。異型化をパラメータ化/インデックス化型で動作させるにはどうすればよいですか?

しかし、すべてのシェイプで機能する統合アプローチを作成できないようです。

代数には「キャリアタイプ」が必要ですが、移動する構造にはある種の形状(それ自体は再帰的に適用される)が必要なので、どのタイプでも運ぶことができる「ダミー」コンテナがあります。期待される。私はタイプファミリーを使ってこれらを結合します。

このアプローチはうまくいくと思われ、私の 'cata'機能のためのかなり一般的な署名につながります。

しかし、私が使っている他のもの(Mu、代数)は、型変数の束を渡すために、それぞれの図形に対して別々のバージョンが必要です。私はPolyKinds(ダミータイプを形作るのにうまく使う)のようなものを期待していましたが、それは他の方法で動作するように意図されているようです。

IFunctor1とIFunctor2には余分な変数がないので、(ファミリを介して)インデックス保存関数型を追加して統一しようとしましたが、これは存在量のために許可されていないようですそこには複数のバージョンもあります。

これらの2つのケースを統一する方法はありますか?私はいくつかのトリックを見落としましたか、これはただの制限ですか? 他に単純化できることはありますか?

{-# LANGUAGE DataKinds     #-} 
{-# LANGUAGE ExistentialQuantification #-} 
{-# LANGUAGE GADTs      #-} 
{-# LANGUAGE PolyKinds     #-} 
{-# LANGUAGE Rank2Types    #-} 
{-# LANGUAGE StandaloneDeriving  #-} 
{-# LANGUAGE TypeFamilies    #-} 
{-# LANGUAGE TypeOperators    #-} 
{-# LANGUAGE UndecidableInstances  #-} 
module Cata where 

-- 'Fix' for indexed types (1 index) 
newtype Mu1 f a = Roll1 { unRoll1 :: f (Mu1 f) a } 
deriving instance Show (f (Mu1 f) a) => Show (Mu1 f a) 

-- 'Fix' for indexed types (2 index) 
newtype Mu2 f a b = Roll2 { unRoll2 :: f (Mu2 f) a b } 
deriving instance Show (f (Mu2 f) a b) => Show (Mu2 f a b) 

-- index-preserving function (1 index) 
type s :-> t = forall i. s i -> t i 

-- index-preserving function (2 index) 
type s :--> t = forall i j. s i j -> t i j 

-- indexed functor (1 index) 
class IFunctor1 f where 
    imap1 :: (s :-> t) -> (f s :-> f t) 

-- indexed functor (2 index) 
class IFunctor2 f where 
    imap2 :: (s :--> t) -> (f s :--> f t) 

-- dummy container type to store a solid result type 
-- the shape should follow an indexed type 
type family Dummy (x :: i -> k) :: * -> k 

type Algebra1 f a = forall t. f ((Dummy f) a) t -> (Dummy f) a t 
type Algebra2 f a = forall s t. f ((Dummy f) a) s t -> (Dummy f) a s t 

cata1 :: IFunctor1 f => Algebra1 f a -> Mu1 f t -> (Dummy f) a t 
cata1 alg = alg . imap1 (cata1 alg) . unRoll1 

cata2 :: IFunctor2 f => Algebra2 f a -> Mu2 f s t -> (Dummy f) a s t 
cata2 alg = alg . imap2 (cata2 alg) . unRoll2 

そして、2例の構造はで動作するように:

ExprF1オブジェクト言語に埋め込み型を取り付け、通常の便利なことのように思えます。

「一般的な」cata2がこれらの形状を処理できるかどうかを調べるために、ExprF2が考案されています(余分な引数(DataKindsも同様))。

-- our indexed type, which we want to use in an F-algebra (1 index) 
data ExprF1 f t where 
    ConstI1 :: Int -> ExprF1 f Int 
    ConstB1 :: Bool -> ExprF1 f Bool 
    Add1 :: f Int -> f Int -> ExprF1 f Int 
    Mul1 :: f Int -> f Int -> ExprF1 f Int 
    If1  :: f Bool -> f t -> f t -> ExprF1 f t 
deriving instance (Show (f t), Show (f Bool)) => Show (ExprF1 f t) 

-- our indexed type, which we want to use in an F-algebra (2 index) 
data ExprF2 f s t where 
    ConstI2 :: Int -> ExprF2 f Int True 
    ConstB2 :: Bool -> ExprF2 f Bool True 
    Add2 :: f Int True -> f Int True -> ExprF2 f Int True 
    Mul2 :: f Int True -> f Int True -> ExprF2 f Int True 
    If2  :: f Bool True -> f t True -> f t True -> ExprF2 f t True 
deriving instance (Show (f s t), Show (f Bool t)) => Show (ExprF2 f s t) 

-- mapper for f-algebra (1 index) 
instance IFunctor1 ExprF1 where 
    imap1 _ (ConstI1 x) = ConstI1 x 
    imap1 _ (ConstB1 x) = ConstB1 x 
    imap1 eval (x `Add1` y) = eval x `Add1` eval y 
    imap1 eval (x `Mul1` y) = eval x `Mul1` eval y 
    imap1 eval (If1 p t e) = If1 (eval p) (eval t) (eval e) 

-- mapper for f-algebra (2 index) 
instance IFunctor2 ExprF2 where 
    imap2 _ (ConstI2 x) = ConstI2 x 
    imap2 _ (ConstB2 x) = ConstB2 x 
    imap2 eval (x `Add2` y) = eval x `Add2` eval y 
    imap2 eval (x `Mul2` y) = eval x `Mul2` eval y 
    imap2 eval (If2 p t e) = If2 (eval p) (eval t) (eval e) 

-- turned into a nested expression 
type Expr1 = Mu1 ExprF1 

-- turned into a nested expression 
type Expr2 = Mu2 ExprF2 

-- dummy containers 
newtype X1 x y = X1 x deriving Show 
newtype X2 x y z = X2 x deriving Show 

type instance Dummy ExprF1 = X1 
type instance Dummy ExprF2 = X2 


-- a simple example agebra that evaluates the expression 
-- turning bools into 0/1  
alg1 :: Algebra1 ExprF1 Int 
alg1 (ConstI1 x)   = X1 x 
alg1 (ConstB1 False)  = X1 0 
alg1 (ConstB1 True)   = X1 1 
alg1 ((X1 x) `Add1` (X1 y)) = X1 $ x + y 
alg1 ((X1 x) `Mul1` (X1 y)) = X1 $ x * y 
alg1 (If1 (X1 0) _ (X1 e)) = X1 e 
alg1 (If1 _ (X1 t) _)  = X1 t 

alg2 :: Algebra2 ExprF2 Int 
alg2 (ConstI2 x)   = X2 x 
alg2 (ConstB2 False)  = X2 0 
alg2 (ConstB2 True)   = X2 1 
alg2 ((X2 x) `Add2` (X2 y)) = X2 $ x + y 
alg2 ((X2 x) `Mul2` (X2 y)) = X2 $ x * y 
alg2 (If2 (X2 0) _ (X2 e)) = X2 e 
alg2 (If2 _ (X2 t) _)  = X2 t 


-- simple helpers for construction 
ci1 :: Int -> Expr1 Int 
ci1 = Roll1 . ConstI1 

cb1 :: Bool -> Expr1 Bool 
cb1 = Roll1 . ConstB1 

if1 :: Expr1 Bool -> Expr1 a -> Expr1 a -> Expr1 a 
if1 p t e = Roll1 $ If1 p t e 

add1 :: Expr1 Int -> Expr1 Int -> Expr1 Int 
add1 x y = Roll1 $ Add1 x y 

mul1 :: Expr1 Int -> Expr1 Int -> Expr1 Int 
mul1 x y = Roll1 $ Mul1 x y 

ci2 :: Int -> Expr2 Int True 
ci2 = Roll2 . ConstI2 

cb2 :: Bool -> Expr2 Bool True 
cb2 = Roll2 . ConstB2 

if2 :: Expr2 Bool True -> Expr2 a True-> Expr2 a True -> Expr2 a True 
if2 p t e = Roll2 $ If2 p t e 

add2 :: Expr2 Int True -> Expr2 Int True -> Expr2 Int True 
add2 x y = Roll2 $ Add2 x y 

mul2 :: Expr2 Int True -> Expr2 Int True -> Expr2 Int True 
mul2 x y = Roll2 $ Mul2 x y 


-- test case 
test1 :: Expr1 Int 
test1 = if1 (cb1 True) 
      (ci1 3 `mul1` ci1 4 `add1` ci1 5) 
      (ci1 2) 

test2 :: Expr2 Int True 
test2 = if2 (cb2 True) 
      (ci2 3 `mul2` ci2 4 `add2` ci2 5) 
      (ci2 2) 


main :: IO() 
main = let (X1 x1) = cata1 alg1 test1 
      (X2 x2) = cata2 alg2 test2 
     in do print x1 
      print x2 

出力:

17 
17 
+4

2つのインデックスを使用するよりも、それはペアの索引へのより均一なのです。私のアドバイスは、インデックス付きセットを扱う際には、可能な限り正確に1つのインデックスを使用し、昇格されたタイプのインデックスを自由に構築することです。 1、2、4、8、べき乗する時間! – pigworker

+1

@pigworker:ああ、それは確かに素晴らしい回避策です。 それから私はcata1と友達だけが必要です。 –

+0

__Good__質問、偉大な人の一人からすばらしい答えを呼び起こす。基本的なものから崇高なものへと進んでいく場所には、多くのタグはありません。 Yay Haskell。 – AndrewC

答えて

12

私は2009年に"Slicing It"と呼ばれるこのトピックに関する話を書いたことは、私のStrathclydeの同僚であるJohannとGhaniによるGADTの初期代数セマンティクスの研究を指摘しています。私はSHEは、データインデックス型を書き込むために提供さ表記を使用し、それは楽しく「プロモーション」の話に取って代わられています。

話のキーポイントは、私のコメントのとおり、正確に一つのインデックスを使用する方法について体系的になることですが、その種類は変わることができるという事実を利用します。

だから、確かに、私たちは(私の現在の好ましい "GoscinnyとUderzo" 名前を使用)している

type s :-> t = forall i. s i -> t i 

class FunctorIx f where 
    mapIx :: (s :-> t) -> (f s :-> f t) 

今、あなたはFunctorIxがfixpoints下を閉じて表示することができます。キーは、2つのインデックス付きセットをインデックスの選択肢を提供するセットに結合することです。

data Case (f :: i -> *) (g :: j -> *) (b :: Either i j) :: * where 
    L :: f i -> Case f g (Left i) 
    R :: g j -> Case f g (Right j) 

(<?>) :: (f :-> f') -> (g :-> g') -> Case f g :-> Case f' g' 
(f <?> g) (L x) = L (f x) 
(f <?> g) (R x) = R (g x) 

今、私たちは今、「要素を含ん」「ペイロード」または「再帰的な基礎構造」のいずれかを表すファンクタのfixpointsを取ることができます。

data MuIx (f :: (Either i j -> *) -> j -> *) :: (i -> *) -> j -> * where 
    InIx :: f (Case x (MuIx f x)) j -> MuIx f x j 
結果

、我々 "ペイロード" を超えるmapIxをすることができます...

instance FunctorIx f => FunctorIx (MuIx f) where 
    mapIx f (InIx xs) = InIx (mapIx (f <?> mapIx f) xs) 

...または "再帰的な基礎構造" を超えるcatamorphismを書く...

foldIx :: FunctorIx f => (f (Case x t) :-> t) -> MuIx f x :-> t 
foldIx f (InIx xs) = f (mapIx (id <?> foldIx f) xs) 

...またはその両方

mapFoldIx :: FunctorIx f => (x :-> y) -> (f (Case y t) :-> t) -> MuIx f x :-> t 
mapFoldIx e f (InIx xs) = f (mapIx (e <?> mapFoldIx e f) xs) 

FunctorIxの喜びは、それは、このような立派な閉鎖特性を有することがインデックスの種類を変化させる能力のおかげです。 MuIxは、ペイロードの概念を可能にし、反復することができます。したがって、複数の指標ではなく構造化された指標で作業するインセンティブがあります。

+1

うわー。それは私の心を少し吹き飛ばした!私はそれがどのように機能するかを幾分見ることができますが、私はそれ自身のようなものを作り出すことしか夢見ることができません。それは私の質問を含め、多くの状況で非常に便利なので、私はこの答えに切り替えるつもりです。あなたは "閉鎖"と "閉鎖のプロパティ"の意味を少し詳しく教えてください。( FunctorIx、FoldIx、friendsはある種のカールトンを思い出させる:P –

+2

索引付けされていないストーリーは 'Mu(f :: * - > *):: * '' Functor f''は再帰的なデータ型のノード構造を記述しています: 'f'は再帰的な部分構造の位置を抽象化しますが、' '要素 '"という概念を持っていても 'リストを考えてみましょう:リストノードには、リスト要素の場所とサブリストの場所があります.1つのノードを記述するバイフナクタの固定点(つまり、Functor)ではなく、しかし、再帰的な索引付けされたファンクタは、他の索引付けされたファンクタの固定点であるため、索引付けされたファンクタは固定点の下で閉じられます。 – pigworker

3

私はそれを正しく理解していれば、これは正確にヨハンとGhaniさんが取り組む問題である「始代数意味論は十分です!」

https://personal.cis.strath.ac.uk/neil.ghani/papers/ghani-tlca07.pdf

参照してください。特に彼らのhfold

編集:GADTのケースでは、彼らの後に紙 "GADTsを使用して構造化プログラミングのための基礎" を参照してください。彼らは我々が今持っているPolyKindsを、使用して解決することができる障害物に遭遇することに注意してください:http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.111.2948

このブログの記事にも興味がある可能性がありますhttp://www.timphilipwilliams.com/posts/2013-01-16-fixing-gadts.html

+0

確かに非常に興味深い論文のように見えます。 はしかし、私は(GADTによるインデックス付けタイプ)けれども、それは私の問題を扱うか分かりません。紙の 最後の段落は、明示的にGADTsが可能な将来の仕事です状態: 「最後に、本論文の手法は、折り目の理論への洞察を提供し構築し、このような混合分散データ型などの高度なデータ型、のための融合規則があり、 GADT、および従属型」 –

+0

クール、多くはそこに完璧なソリューションがあるかどう はおそらく見つけるために私にいくつかの時間がかかります:)読むために、私はそれが私の右方向へのステップの多くを与えるつもりだと確信していますので、私は受け入れるつもりですあなたの答え。ありがとう! –

関連する問題