Jagger/Richards: vous ne pouvez pas toujours obtenir ce que vous voulez, mais si vous essayez parfois, vous pourriez trouver que vous obtenez ce dont vous avez besoin.
Les curseurs dans les Listes
Permettez-moi de reconstruire les composants de votre structure à l'aide de snoc - et le contre-listes de garder les propriétés spatiales clair. Je définir
data Bwd x = B0 | Bwd x :< x deriving (Functor, Foldable, Traversable, Show)
data Fwd x = F0 | x :> Fwd x deriving (Functor, Foldable, Traversable, Show)
infixl 5 :<
infixr 5 :>
data Cursor x = Cur (Bwd x) x (Fwd x) deriving (Functor, Foldable, Traversable, Show)
Nous allons avoir comonads
class Functor f => Comonad f where
counit :: f x -> x
cojoin :: f x -> f (f x)
et faisons en sorte que les curseurs sont comonads
instance Comonad Cursor where
counit (Cur _ x _) = x
cojoin c = Cur (lefts c) c (rights c) where
lefts (Cur B0 _ _) = B0
lefts (Cur (xz :< x) y ys) = lefts c :< c where c = Cur xz x (y :> ys)
rights (Cur _ _ F0) = F0
rights (Cur xz x (y :> ys)) = c :> rights c where c = Cur (xz :< x) y ys
Si vous avez activé pour ce genre de choses, vous remarquerez qu' Cursor
est un espace agréable variante de l' InContext []
InContext f x = (x, ∂f x)
où ∂ prend le formel dérivé d'un foncteur, donnant à sa notion d'un trou de contexte. InContext f
est toujours un Comonad
, comme indiqué dans cette réponse, et de ce que nous avons ici est juste qu' Comonad
induite par la différentielle de la structure, où counit
extraits de l'élément de la mise au point et l' cojoin
décore chaque élément avec son propre contexte, effectivement vous donner un contexte plein de recentrage des curseurs et avec un curseur immobile à son foyer. Prenons un exemple.
> cojoin (Cur (B0 :< 1) 2 (3 :> 4 :> F0))
Cur (B0 :< Cur B0 1 (2 :> 3 :> 4 :> F0))
(Cur (B0 :< 1) 2 (3 :> 4 :> F0))
( Cur (B0 :< 1 :< 2) 3 (4 :> F0)
:> Cur (B0 :< 1 :< 2 :< 3) 4 F0
:> F0)
Voir? Le 2 focus a été décoré de devenir le curseur à 2; à la gauche, nous avons la liste de déplacement du curseur à l'-1; à droite, la liste de déplacement du curseur-3 et le curseur à 4.
La Composition De Curseurs, De La Transposition De Curseurs?
Maintenant, la structure que vous demandez à être un Comonad
est le n fois la composition de l' Cursor
. Nous allons avoir
newtype (:.:) f g x = C {unC :: f (g x)} deriving Show
Pour convaincre comonads f
et g
de composer, de l' counit
s composer avec soin, mais vous avez besoin d'une "loi de distributivité"
transpose :: f (g x) -> g (f x)
ainsi vous pouvez faire le composite cojoin
comme ceci
f (g x)
-(fmap cojoin)->
f (g (g x))
-cojoin->
f (f (g (g x)))
-(fmap transpose)->
f (g (f (g x)))
Quelles sont les lois qui doivent transpose
satisfaire? Probablement quelque chose comme
counit . transpose = fmap counit
cojoin . transpose = fmap transpose . transpose . fmap cojoin
ou ce qu'il faut pour s'assurer que toutes les deux façons de shoogle une séquence de f et de g à partir d'un ordre à l'autre donnent le même résultat.
Peut-on définir une transpose
pour Cursor
avec lui-même? Une manière d'obtenir une sorte de transposition à bas prix est à noter que l' Bwd
et Fwd
sont zippily applicative, d'où donc est - Cursor
.
instance Applicative Bwd where
pure x = pure x :< x
(fz :< f) <*> (sz :< s) = (fz <*> sz) :< f s
_ <*> _ = B0
instance Applicative Fwd where
pure x = x :> pure x
(f :> fs) <*> (s :> ss) = f s :> (fs <*> ss)
_ <*> _ = F0
instance Applicative Cursor where
pure x = Cur (pure x) x (pure x)
Cur fz f fs <*> Cur sz s ss = Cur (fz <*> sz) (f s) (fs <*> ss)
Et ici, vous devriez commencer à sentir le rat. La forme résulte de la troncature, et qui va se casser la évidemment souhaitable bien que l'auto-transposition est auto-inverse. Tout type de raggedness ne survivra pas. Nous ne obtenir une transposition de l'opérateur: sequenceA
, et pour des données régulières, tout est brillant et beau.
> regularMatrixCursor
Cur (B0 :< Cur (B0 :< 1) 2 (3 :> F0))
(Cur (B0 :< 4) 5 (6 :> F0))
(Cur (B0 :< 7) 8 (9 :> F0) :> F0)
> sequenceA regularMatrixCursor
Cur (B0 :< Cur (B0 :< 1) 4 (7 :> F0))
(Cur (B0 :< 2) 5 (8 :> F0))
(Cur (B0 :< 3) 6 (9 :> F0) :> F0)
Mais même si je viens de passer l'un de l'intérieur de curseurs de l'alignement (jamais de mal à faire les tailles en lambeaux), les choses aller à vau-l'eau.
> raggedyMatrixCursor
Cur (B0 :< Cur ((B0 :< 1) :< 2) 3 F0)
(Cur (B0 :< 4) 5 (6 :> F0))
(Cur (B0 :< 7) 8 (9 :> F0) :> F0)
> sequenceA raggedyMatrixCursor
Cur (B0 :< Cur (B0 :< 2) 4 (7 :> F0))
(Cur (B0 :< 3) 5 (8 :> F0))
F0
Lorsque vous avez une extérieure de la position du curseur et de multiples intérieure de la position du curseur, il n'y a pas de transposition qui va bien se comporter. Auto-composition Cursor
permet à l'intérieur des structures afin d'être déséquilibrée par rapport à l'autre, donc pas de transpose
, n cojoin
. Vous pouvez, et je l'ai fait, définir
instance (Comonad f, Traversable f, Comonad g, Applicative g) =>
Comonad (f :.: g) where
counit = counit . counit . unC
cojoin = C . fmap (fmap C . sequenceA) . cojoin . fmap cojoin . unC
mais c'est un fardeau sur nous pour nous assurer de garder l'intérieur des structures régulières. Si vous êtes prêt à accepter cette charge, alors vous pouvez parcourir, car Applicative
et Traversable
sont facilement fermé en vertu de composition. Voici les pièces et de morceaux de
instance (Functor f, Functor g) => Functor (f :.: g) where
fmap h (C fgx) = C (fmap (fmap h) fgx)
instance (Applicative f, Applicative g) => Applicative (f :.: g) where
pure = C . pure . pure
C f <*> C s = C (pure (<*>) <*> f <*> s)
instance (Functor f, Foldable f, Foldable g) => Foldable (f :.: g) where
fold = fold . fmap fold . unC
instance (Traversable f, Traversable g) => Traversable (f :.: g) where
traverse h (C fgx) = C <$> traverse (traverse h) fgx
Edit: pour être complet, voici ce qu'il fait quand tout est régulier,
> cojoin (C regularMatrixCursor)
C {unC = Cur (B0 :< Cur (B0 :<
C {unC = Cur B0 (Cur B0 1 (2 :> (3 :> F0))) (Cur B0 4 (5 :> (6 :> F0)) :> (Cur B0 7 (8 :> (9 :> F0)) :> F0))})
(C {unC = Cur B0 (Cur (B0 :< 1) 2 (3 :> F0)) (Cur (B0 :< 4) 5 (6 :> F0) :> (Cur (B0 :< 7) 8 (9 :> F0) :> F0))})
(C {unC = Cur B0 (Cur ((B0 :< 1) :< 2) 3 F0) (Cur ((B0 :< 4) :< 5) 6 F0 :> (Cur ((B0 :< 7) :< 8) 9 F0 :> F0))} :> F0))
(Cur (B0 :<
C {unC = Cur (B0 :< Cur B0 1 (2 :> (3 :> F0))) (Cur B0 4 (5 :> (6 :> F0))) (Cur B0 7 (8 :> (9 :> F0)) :> F0)})
(C {unC = Cur (B0 :< Cur (B0 :< 1) 2 (3 :> F0)) (Cur (B0 :< 4) 5 (6 :> F0)) (Cur (B0 :< 7) 8 (9 :> F0) :> F0)})
(C {unC = Cur (B0 :< Cur ((B0 :< 1) :< 2) 3 F0) (Cur ((B0 :< 4) :< 5) 6 F0) (Cur ((B0 :< 7) :< 8) 9 F0 :> F0)} :> F0))
(Cur (B0 :<
C {unC = Cur ((B0 :< Cur B0 1 (2 :> (3 :> F0))) :< Cur B0 4 (5 :> (6 :> F0))) (Cur B0 7 (8 :> (9 :> F0))) F0})
(C {unC = Cur ((B0 :< Cur (B0 :< 1) 2 (3 :> F0)) :< Cur (B0 :< 4) 5 (6 :> F0)) (Cur (B0 :< 7) 8 (9 :> F0)) F0})
(C {unC = Cur ((B0 :< Cur ((B0 :< 1) :< 2) 3 F0) :< Cur ((B0 :< 4) :< 5) 6 F0) (Cur ((B0 :< 7) :< 8) 9 F0) F0} :> F0)
:> F0)}
Hancock Tenseur Produit
Pour la régularité, vous avez besoin de quelque chose de plus fort que la composition. Vous devez être en mesure de capturer la notion de "f-structure de g-structures-de-tous-les-mêmes-forme". C'est ce que l'inestimable Peter Hancock appelle le "tenseur produit", je vais écrire f :><: g
: il y a un "extérieur" f-forme et un "intérieur" g-forme commune à tous les intérieurs g-structures, afin que la transposition est facilement définissable et toujours auto-inverse. Hancock tenseur n'est pas facilement définissable en Haskell, mais dans un dépendante tapé, il est facile de formuler une notion de "conteneur" qui a ce tenseur.
Pour vous donner l'idée, imaginez un dégénéré notion de conteneur
data (:<|) s p x = s :<| (p -> x)
lorsque nous disons s
est le type de "formes" et p
le type de "positions". Une valeur consiste en le choix d'une forme et le stockage de l' x
dans chaque position. Dans le dépendantes cas, le type de postes peut dépendre du choix de la forme (par exemple, pour les listes, la forme est un nombre (la longueur), et vous avez plusieurs postes). Ces conteneurs ont un tenseur produit
(s :<| p) :><: (s' :<| p') = (s, s') :<| (p, p')
qui est comme une généralisé de la matrice: une paire de formes donner les dimensions, et puis vous avez un élément à chaque paire de positions. Vous pouvez le faire parfaitement bien quand les types de p
et p'
reposent sur des valeurs en s
et s'
, et c'est exactement Hancock définition du tenseur des produits de conteneurs.
InContext pour les Produits Tensoriels
Maintenant, comme vous l'avez appris à l'école secondaire, ∂(s :<| p) = (s, p) :<| (p-1)
où p-1
est un type avec un moins de l'élément qu' p
. Comme ∂(s*x^p) = (s*p)*x^(p-1). Vous sélectionnez une position d'enregistrement (dans la forme) et de le supprimer. Le hic, c'est qu' p-1
est difficile à obtenir vos mains sur sans des types dépendants. Mais InContext
sélectionne une position sans la supprimer.
InContext (s :<| p) ~= (s, p) :<| p
Cela fonctionne aussi bien pour les dépendants cas, et nous avons joyeusement acquérir
InContext (f :><: g) ~= InContext f :><: InContext g
Maintenant, nous savons que InContext f
est toujours un Comonad
, et cela nous indique que les produits tensoriels de InContext
s sont comonadic parce qu'ils sont eux-mêmes InContext
s. C'est-à-dire, vous prenez une position par dimension (et qui vous donne exactement la même position dans l'ensemble de la chose), là où avant on avait une position externe et beaucoup d'intérieure positions. Avec le tenseur de produits de remplacement de la composition, tout fonctionne doucement.
Naperian Foncteurs
Mais il y a une sous-classe de Functor
pour qui le tenseur du produit et de la composition de coïncider. Ce sont les Functor
s f
dont f () ~ ()
: c'est à dire, il n'y a qu'une forme de toute façon, si fruste valeurs dans des compositions sont exclues dans la première place. Ces Functor
s sont tous isomorphe a' (p ->)
pour certains de la position d' p
qui nous pouvons penser que le logarithme (en l'exposant à qui x
doit être soulevée pour donner des f x
). En conséquence, Hancock appelle ces Naperian
foncteurs d'après John Napier (dont le fantôme hante le cadre d'Edimbourg, où Hancock vie).
class Applicative f => Naperian f where
type Log f
project :: f x -> Log f -> x
positions :: f (Log f)
--- project positions = id
Un Naperian
foncteur a un logarithme, induisant un project
ion fonction de la cartographie des positions des éléments que l'on y trouve. Naperian
foncteurs sont tous zippily Applicative
, pure
et <*>
correspondant à K et S combinators pour les projections. Il est également possible de construire une valeur à chaque position est stockée la même position de la représentation. Les lois des logarithmes dont vous pouvez vous souvenir de pop up agréablement.
newtype Id x = Id {unId :: x} deriving Show
instance Naperian Id where
type Log Id = ()
project (Id x) () = x
positions = Id ()
newtype (:*:) f g x = Pr (f x, g x) deriving Show
instance (Naperian f, Naperian g) => Naperian (f :*: g) where
type Log (f :*: g) = Either (Log f) (Log g)
project (Pr (fx, gx)) (Left p) = project fx p
project (Pr (fx, gx)) (Right p) = project gx p
positions = Pr (fmap Left positions, fmap Right positions)
Notez qu'un tableau de taille fixe (un vecteur) est donnée par (Id :*: Id :*: ... :*: Id :*: One)
où One
est la constante de l'unité de foncteur, dont le logarithme est - Void
. Si un tableau est Naperian
. Maintenant, nous avons aussi
instance (Naperian f, Naperian g) => Naperian (f :.: g) where
type Log (f :.: g) = (Log f, Log g)
project (C fgx) (p, q) = project (project fgx p) q
positions = C $ fmap (\ p -> fmap (p ,) positions) positions
ce qui signifie que le multi-dimensions des tableaux sont en Naperian
.
Pour construire une version de InContext f
pour Naperian f
, il suffit de pointer vers une position!
data Focused f x = f x :@ Log f
instance Functor f => Functor (Focused f) where
fmap h (fx :@ p) = fmap h fx :@ p
instance Naperian f => Comonad (Focused f) where
counit (fx :@ p) = project fx p
cojoin (fx :@ p) = fmap (fx :@) positions :@ p
Donc, en particulier, un Focused
n-dimensions tableau sera en effet une comonad. Une composition de vecteurs est un tenseur produit de n vecteurs, car les vecteurs sont Naperian
. Mais l' Focused
n-dimensions tableau sera la n-fold tenseur produit, pas la composition, de la n Focused
vecteurs de déterminer ses dimensions. Pour exprimer cette comonad en termes de fermetures à glissière, nous allons avoir besoin de les exprimer sous une forme qui permet de construire le tenseur du produit. Je vais laisser ça comme un exercice pour l'avenir.