一般拉链拉链


80

对于任何容器类型,我们都可以形成(以元素为中心)拉链,并知道此结构是Comonad。最近在另一个Stack Overflow问题中针对以下类型对此进行了详细探讨:

data Bin a = Branch (Bin a) a (Bin a) | Leaf a deriving Functor

使用以下拉链

data Dir = L | R
data Step a = Step a Dir (Bin a)   deriving Functor
data Zip  a = Zip [Step a] (Bin a) deriving Functor
instance Comonad Zip where ...

这是一个情况ZipComonad,虽然它的实例的建设是一个有点毛。也就是说,Zip可以完全机械地派生,Tree并且(我相信)以这种方式派生的任何类型都将自动为a Comonad,因此我认为应该可以通用且自动地构造这些类型及其同名物。

实现拉链构造通用性的一种方法是使用以下类和类型族

data Zipper t a = Zipper { diff :: D t a, here :: a }

deriving instance Diff t => Functor (Zipper t)

class (Functor t, Functor (D t)) => Diff t where
  data D t :: * -> *
  inTo  :: t a -> t (Zipper t a)
  outOf :: Zipper t a -> t a

(或多或少)出现在Haskell Cafe主题和Conal Elliott的博客中。可以为各种核心代数类型实例化此类,从而提供了讨论ADT派生类的通用框架。

所以,最终,我的问题是我们是否可以写

instance Diff t => Comonad (Zipper t) where ...

可以用来包含上述特定的Comonad实例:

instance Diff Bin where
  data D Bin a = DBin { context :: [Step a], descend :: Maybe (Bin a, Bin a) }
  ...

不幸的是,我没有写过这样的实例的运气。是inTo/outOf签名是否足够?还需要其他约束类型吗?这个实例有可能吗?


29
给我们一点时间...
猪工2014年

您对DiffforEither和for的实现有参考(,)吗?我有一个想解决的天真简单的解决方案。
Cirdec

@Cirdec您不一定要为都实现它,而是要为Either1 f g x = Inl (f x) | Inr (g x)Conal的博客中有完整的详细信息。
J. Abrahamson

实际上,假设您可以指向至少一个目标值Either则不能在此框架中完全实现(并且希望此问题的正确答案可以解决此问题)。认真地,这对于“空”类型是不可能的。Zipper
J. Abrahamson

3
@Patrick这个问题实际上是相当精确的,尽管它基于相当先进的Haskell功能。Cirdec的最后答案没有那么久。养猪工人习惯于使自己的回答非常透彻,这是另一回事,大多数人对此表示赞赏。
与Orjan约翰森

Answers:


113

就像Chitty-Chitty-Bang-Bang中吸引孩子的人一样,他们将糖果和玩具吸引到囚禁中,大学物理专业的招聘人员喜欢用肥皂泡和飞旋镖来糊弄,但是当门c关闭时,这是“对了,孩子们,有时间学习关于部分分化!”。我也是。不要说我没有警告过你。

下面是另一个警告:下面的代码需要{-# LANGUAGE KitchenSink #-},或者更确切地说,

{-# LANGUAGE TypeFamilies, FlexibleContexts, TupleSections, GADTs, DataKinds,
    TypeOperators, FlexibleInstances, RankNTypes, ScopedTypeVariables,
    StandaloneDeriving, UndecidableInstances #-}

没有特别的顺序。

可微分的函子提供普通拉链

到底什么是可微函子?

class (Functor f, Functor (DF f)) => Diff1 f where
  type DF f :: * -> *
  upF      ::  ZF f x  ->  f x
  downF    ::  f x     ->  f (ZF f x)
  aroundF  ::  ZF f x  ->  ZF f (ZF f x)

data ZF f x = (:<-:) {cxF :: DF f x, elF :: x}

它是一个具有导数的函子,它也是一个函子。导数表示元素的单孔上下文。拉链类型ZF f x表示一对单孔上下文和孔中的元素。

这些操作Diff1描述了我们可以在拉链上进行的导航类型(没有“向左”和“向右”的概念,有关这些信息,请参见我的《小丑和小丑》论文)。我们可以“向上”,通过将元素插入孔中来重新组装结构。我们可以“向下”,找到在给定结构中访问元素的各种方法:我们用元素的上下文装饰每个元素。我们可以“四处走走”,使用现有的拉链并用其上下文装饰每个元素,因此我们找到了重新聚焦的所有方法(以及如何保持当前的聚焦)。

现在,类型aroundF可能会提醒您一些

class Functor c => Comonad c where
  extract    :: c x -> x
  duplicate  :: c x -> c (c x)

提醒您,您是对的!我们有一跳又一跳

instance Diff1 f => Functor (ZF f) where
  fmap f (df :<-: x) = fmap f df :<-: f x

instance Diff1 f => Comonad (ZF f) where
  extract    = elF
  duplicate  = aroundF

我们坚持认为

extract . duplicate == id
fmap extract . duplicate == id
duplicate . duplicate == fmap duplicate . duplicate

我们还需要

fmap extract (downF xs) == xs              -- downF decorates the element in position
fmap upF (downF xs) = fmap (const xs) xs   -- downF gives the correct context

多项式函子是可微的

常数函子是可微的。

data KF a x = KF a
instance Functor (KF a) where
  fmap f (KF a) = KF a

instance Diff1 (KF a) where
  type DF (KF a) = KF Void
  upF (KF w :<-: _) = absurd w
  downF (KF a) = KF a
  aroundF (KF w :<-: _) = absurd w

无处可放元素,因此不可能形成上下文。还有的无处可去upFdownF从,我们不难发现所有无的去的方式downF

标识仿函数是可微。

data IF x = IF x
instance Functor IF where
  fmap f (IF x) = IF (f x)

instance Diff1 IF where
  type DF IF = KF ()
  upF (KF () :<-: x) = IF x
  downF (IF x) = IF (KF () :<-: x)
  aroundF z@(KF () :<-: x) = KF () :<-: z

琐碎的上下文中只有一个元素,downF找到它,重新upF包装它,aroundF只能保持原状。

总和保留差异性。

data (f :+: g) x = LF (f x) | RF (g x)
instance (Functor f, Functor g) => Functor (f :+: g) where
  fmap h (LF f) = LF (fmap h f)
  fmap h (RF g) = RF (fmap h g)

instance (Diff1 f, Diff1 g) => Diff1 (f :+: g) where
  type DF (f :+: g) = DF f :+: DF g
  upF (LF f' :<-: x) = LF (upF (f' :<-: x))
  upF (RF g' :<-: x) = RF (upF (g' :<-: x))

其他零碎的部分很少。首先downF,我们必须进入downF加标签的组件内部,然后修复生成的拉链以在上下文中显示标签。

  downF (LF f) = LF (fmap (\ (f' :<-: x) -> LF f' :<-: x) (downF f))
  downF (RF g) = RF (fmap (\ (g' :<-: x) -> RF g' :<-: x) (downF g))

首先aroundF,我们剥离标签,弄清楚如何处理未加标签的东西,然后将标签恢复到所有产生的拉链中。焦点位于的元素x被其整个拉链代替z

  aroundF z@(LF f' :<-: (x :: x)) =
    LF (fmap (\ (f' :<-: x) -> LF f' :<-: x) . cxF $ aroundF (f' :<-: x :: ZF f x))
    :<-: z
  aroundF z@(RF g' :<-: (x :: x)) =
    RF (fmap (\ (g' :<-: x) -> RF g' :<-: x) . cxF $ aroundF (g' :<-: x :: ZF g x))
    :<-: z

请注意,我不得不使用ScopedTypeVariables来消除对的递归调用的歧义aroundF。作为类型函数,DF不是单射的,因此f' :: D f x不足以强制使用这一事实f' :<-: x :: Z f x

产品保留了差异性。

data (f :*: g) x = f x :*: g x
instance (Functor f, Functor g) => Functor (f :*: g) where
  fmap h (f :*: g) = fmap h f :*: fmap h g

要专注于成对的元素,您可以专注于左侧,而不理会右侧,反之亦然。莱布尼兹著名的乘积法则与简单的空间直觉相对应!

instance (Diff1 f, Diff1 g) => Diff1 (f :*: g) where
  type DF (f :*: g) = (DF f :*: g) :+: (f :*: DF g)
  upF (LF (f' :*: g) :<-: x) = upF (f' :<-: x) :*: g
  upF (RF (f :*: g') :<-: x) = f :*: upF (g' :<-: x)

现在,其downF工作方式与求和方式类似,不同之处在于,我们不仅必须使用标签(以显示我们走过的路)来修复拉链上下文,还必须使用未接触到的其他组件来修复拉链上下文。

  downF (f :*: g)
    =    fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x) (downF f)
    :*:  fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x) (downF g)

aroundF是一大堆笑声。无论我们当前正在访问哪一侧,我们都有两种选择:

  1. aroundF那边移动。
  2. 从那upF一侧downF移到另一侧。

每种情况都要求我们利用子结构的操作,然后修正上下文。

  aroundF z@(LF (f' :*: g) :<-: (x :: x)) =
    LF (fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x)
          (cxF $ aroundF (f' :<-: x :: ZF f x))
        :*: fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x) (downF g))
    :<-: z
    where f = upF (f' :<-: x)
  aroundF z@(RF (f :*: g') :<-: (x :: x)) =
    RF (fmap (\ (f' :<-: x) -> LF (f' :*: g) :<-: x) (downF f) :*:
        fmap (\ (g' :<-: x) -> RF (f :*: g') :<-: x)
          (cxF $ aroundF (g' :<-: x :: ZF g x)))
    :<-: z
    where g = upF (g' :<-: x)

!多项式都是可微的,因此给我们带来了共鸣。

嗯 有点抽象。所以我deriving Show尽我所能地加入了

deriving instance (Show (DF f x), Show x) => Show (ZF f x)

允许进行以下互动(手动整理)

> downF (IF 1 :*: IF 2)
IF (LF (KF () :*: IF 2) :<-: 1) :*: IF (RF (IF 1 :*: KF ()) :<-: 2)

> fmap aroundF it
IF  (LF (KF () :*: IF (RF (IF 1 :*: KF ()) :<-: 2)) :<-: (LF (KF () :*: IF 2) :<-: 1))
:*:
IF  (RF (IF (LF (KF () :*: IF 2) :<-: 1) :*: KF ()) :<-: (RF (IF 1 :*: KF ()) :<-: 2))

练习表明,使用链规则可微分函子的组成是可微分的。

甜!我们现在可以回家吗?当然不是。我们尚未区分任何递归结构。

用bifunctors构造递归函

Bifunctor,如在数据类型通用编程的现有文献(由帕特里克杨松和Johan Jeuring,或由Jeremy长臂猿优良讲义看到工作)说明在长度为一个类型构造具有两个参数,对应于两类子结构。我们应该能够“映射”两者。

class Bifunctor b where
  bimap :: (x -> x') -> (y -> y') -> b x y -> b x' y'

我们可以使用Bifunctors给出递归容器的节点结构。每个节点都有子节点元素。这些可能只是两种子结构。

data Mu b y = In (b (Mu b y) y)

看到?我们在b第一个参数中“打结递归结” ,并将参数保留y在第二个参数中。因此,我们一劳永逸

instance Bifunctor b => Functor (Mu b) where
  fmap f (In b) = In (bimap (fmap f) f b)

要使用此功能,我们需要一套Bifunctor实例。

Bifunctor套件

常数是双功能的。

newtype K a x y = K a

instance Bifunctor (K a) where
  bimap f g (K a) = K a

您可以说我首先写了这一点,因为标识符较短,但这很好,因为代码较长。

变量是双功能的。

我们需要对应于一个参数或另一个参数的双功能键,因此我创建了一种数据类型以区分它们,然后定义了合适的GADT。

data Var = X | Y

data V :: Var -> * -> * -> * where
  XX :: x -> V X x y
  YY :: y -> V Y x y

制作V X x y的副本xV Y x y的副本y。相应地

instance Bifunctor (V v) where
  bimap f g (XX x) = XX (f x)
  bimap f g (YY y) = YY (g y)

资金产品bifunctors是bifunctors

data (:++:) f g x y = L (f x y) | R (g x y) deriving Show

instance (Bifunctor b, Bifunctor c) => Bifunctor (b :++: c) where
  bimap f g (L b) = L (bimap f g b)
  bimap f g (R b) = R (bimap f g b)

data (:**:) f g x y = f x y :**: g x y deriving Show

instance (Bifunctor b, Bifunctor c) => Bifunctor (b :**: c) where
  bimap f g (b :**: c) = bimap f g b :**: bimap f g c

到目前为止,样板好,但是现在我们可以定义类似

List = Mu (K () :++: (V Y :**: V X))

Bin = Mu (V Y :**: (K () :++: (V X :**: V X)))

如果您想将这些类型用于实际数据,而又不盲目地模仿Georges Seurat的传统,请使用模式同义词

但是拉链呢?我们如何证明它Mu b是可区分的?我们将需要证明这两个变量b是可微的。铛!现在是时候了解部分差异化了。

双功能的偏导数

因为我们有两个变量,所以我们将需要能够有时在其他时间集体讨论它们。我们将需要单身家庭:

data Vary :: Var -> * where
  VX :: Vary X
  VY :: Vary Y

现在我们可以说说Bifunctor在每个变量中具有偏导数并给出相应的zipper概念。

class (Bifunctor b, Bifunctor (D b X), Bifunctor (D b Y)) => Diff2 b where
  type D b (v :: Var) :: * -> * -> *
  up      :: Vary v -> Z b v x y -> b x y
  down    :: b x y -> b (Z b X x y) (Z b Y x y)
  around  :: Vary v -> Z b v x y -> Z b v (Z b X x y) (Z b Y x y)

data Z b v x y = (:<-) {cxZ :: D b v x y, elZ :: V v x y}

D操作需要知道要定位的变量。相应的拉链Z b v告诉我们哪个变量v必须是焦点。当我们“用上下文装饰”时,我们必须xX-contexts装饰-elements和yY-contexts装饰-elements。但除此之外,这是同一回事。

我们还有两个任务:首先,证明我们的bifunctor套件是可区分的。其次,表明Diff2 b允许我们建立Diff1 (Mu b)

区分Bifunctor套件

恐怕这不是摆弄,而是有趣。随时跳过。

常数与以前一样。

instance Diff2 (K a) where
  type D (K a) v = K Void
  up _ (K q :<- _) = absurd q
  down (K a) = K a
  around _ (K q :<- _) = absurd q

在这种情况下,生命太短了,无法发展类型级别的Kronecker-delta的理论,因此我只对变量进行了单独处理。

instance Diff2 (V X) where
  type D (V X) X = K ()
  type D (V X) Y = K Void
  up VX (K () :<- XX x)  = XX x
  up VY (K q :<- _)      = absurd q
  down (XX x) = XX (K () :<- XX x)
  around VX z@(K () :<- XX x)  = K () :<- XX z
  around VY (K q :<- _)        = absurd q

instance Diff2 (V Y) where
  type D (V Y) X = K Void
  type D (V Y) Y = K ()
  up VX (K q :<- _)      = absurd q
  up VY (K () :<- YY y)  = YY y
  down (YY y) = YY (K () :<- YY y)
  around VX (K q :<- _)        = absurd q
  around VY z@(K () :<- YY y)  = K () :<- YY z

对于结构性案例,我发现引入帮助程序使我能够统一处理变量非常有用。

vV :: Vary v -> Z b v x y -> V v (Z b X x y) (Z b Y x y)
vV VX z = XX z
vV VY z = YY z

然后,我构建了小工具,以方便我们需要down和进行的“重新标记” around。(当然,我在工作时看到了需要哪些小工具。)

zimap :: (Bifunctor c) => (forall v. Vary v -> D b v x y -> D b' v x y) ->
         c (Z b X x y) (Z b Y x y) -> c (Z b' X x y) (Z b' Y x y)
zimap f = bimap
  (\ (d :<- XX x) -> f VX d :<- XX x)
  (\ (d :<- YY y) -> f VY d :<- YY y)

dzimap :: (Bifunctor (D c X), Bifunctor (D c Y)) =>
         (forall v. Vary v -> D b v x y -> D b' v x y) ->
         Vary v -> Z c v (Z b X x y) (Z b Y x y) -> D c v (Z b' X x y) (Z b' Y x y)
dzimap f VX (d :<- _) = bimap
  (\ (d :<- XX x) -> f VX d :<- XX x)
  (\ (d :<- YY y) -> f VY d :<- YY y)
  d
dzimap f VY (d :<- _) = bimap
  (\ (d :<- XX x) -> f VX d :<- XX x)
  (\ (d :<- YY y) -> f VY d :<- YY y)
  d

准备好了很多之后,我们就可以详细研究一下。求和很容易。

instance (Diff2 b, Diff2 c) => Diff2 (b :++: c) where
  type D (b :++: c) v = D b v :++: D c v
  up v (L b' :<- vv) = L (up v (b' :<- vv))
  down (L b) = L (zimap (const L) (down b))
  down (R c) = R (zimap (const R) (down c))
  around v z@(L b' :<- vv :: Z (b :++: c) v x y)
    = L (dzimap (const L) v ba) :<- vV v z
    where ba = around v (b' :<- vv :: Z b v x y)
  around v z@(R c' :<- vv :: Z (b :++: c) v x y)
    = R (dzimap (const R) v ca) :<- vV v z
    where ca = around v (c' :<- vv :: Z c v x y)

产品是艰苦的工作,这就是为什么我是数学家而不是工程师。

instance (Diff2 b, Diff2 c) => Diff2 (b :**: c) where
  type D (b :**: c) v = (D b v :**: c) :++: (b :**: D c v)
  up v (L (b' :**: c) :<- vv) = up v (b' :<- vv) :**: c
  up v (R (b :**: c') :<- vv) = b :**: up v (c' :<- vv)
  down (b :**: c) =
    zimap (const (L . (:**: c))) (down b) :**: zimap (const (R . (b :**:))) (down c)
  around v z@(L (b' :**: c) :<- vv :: Z (b :**: c) v x y)
    = L (dzimap (const (L . (:**: c))) v ba :**:
        zimap (const (R . (b :**:))) (down c))
      :<- vV v z where
      b = up v (b' :<- vv :: Z b v x y)
      ba = around v (b' :<- vv :: Z b v x y)
  around v z@(R (b :**: c') :<- vv :: Z (b :**: c) v x y)
    = R (zimap (const (L . (:**: c))) (down b):**:
        dzimap (const (R . (b :**:))) v ca)
      :<- vV v z where
      c = up v (c' :<- vv :: Z c v x y)
      ca = around v (c' :<- vv :: Z c v x y)

从概念上讲,它和以前一样,但是官僚机构更多。我使用pre-type-hole技术构建了这些文件,undefined在我还没准备好工作的地方将其用作存根,并在某个地方(在任何给定的时间)引入了故意的类型错误,在那里我需要来自typechecker的有用提示。您也可以在电子游戏方面进行类型检查,即使在Haskell中也是如此。

递归容器的子节点拉链

的偏导数b相对于X告诉我们如何找到一个节点内的子节点一步,所以我们得到的拉链的传统观念。

data MuZpr b y = MuZpr
  {  aboveMu  :: [D b X (Mu b y) y]
  ,  hereMu   :: Mu b y
  }

通过重复插入X位置,我们可以一直放大到根。

muUp :: Diff2 b => MuZpr b y -> Mu b y
muUp (MuZpr {aboveMu = [], hereMu = t}) = t
muUp (MuZpr {aboveMu = (dX : dXs), hereMu = t}) =
  muUp (MuZpr {aboveMu = dXs, hereMu = In (up VX (dX :<- XX t))})

但是我们需要元素-zippers。

元素拉链固定点的元素拉链

每个元素都在节点内部。该节点位于X-衍生物堆栈下。但是元素在该节点中的位置由Y-导数给出。我们得到

data MuCx b y = MuCx
  {  aboveY  :: [D b X (Mu b y) y]
  ,  belowY  :: D b Y (Mu b y) y
  }

instance Diff2 b => Functor (MuCx b) where
  fmap f (MuCx { aboveY = dXs, belowY = dY }) = MuCx
    {  aboveY  = map (bimap (fmap f) f) dXs
    ,  belowY  = bimap (fmap f) f dY
    }

我大胆地说

instance Diff2 b => Diff1 (Mu b) where
  type DF (Mu b) = MuCx b

但是在进行操作之前,我需要点点滴滴。

我可以在functor-zippers和bifunctor-zippers之间交换数据,如下所示:

zAboveY :: ZF (Mu b) y -> [D b X (Mu b y) y]  -- the stack of `X`-derivatives above me
zAboveY (d :<-: y) = aboveY d

zZipY :: ZF (Mu b) y -> Z b Y (Mu b y) y      -- the `Y`-zipper where I am
zZipY (d :<-: y) = belowY d :<- YY y

这足以让我定义:

  upF z  = muUp (MuZpr {aboveMu = zAboveY z, hereMu = In (up VY (zZipY z))})

也就是说,我们首先重新组装元素所在的节点,然后将元素拉链变成子节点拉链,然后像上面一样一直缩小。

接下来,我说

  downF  = yOnDown []

从空堆栈开始向下,并定义down从任何堆栈下面重复出现的helper函数:

yOnDown :: Diff2 b => [D b X (Mu b y) y] -> Mu b y -> Mu b (ZF (Mu b) y)
yOnDown dXs (In b) = In (contextualize dXs (down b))

现在,down b仅将我们带入节点内。我们需要的拉链还必须带有节点的上下文。那是什么contextualise

contextualize :: (Bifunctor c, Diff2 b) =>
  [D b X (Mu b y) y] ->
  c (Z b X (Mu b y) y) (Z b Y (Mu b y) y) ->
  c (Mu b (ZF (Mu b) y)) (ZF (Mu b) y)
contextualize dXs = bimap
  (\ (dX :<- XX t) -> yOnDown (dX : dXs) t)
  (\ (dY :<- YY y) -> MuCx {aboveY = dXs, belowY = dY} :<-: y)

对于每个Y位置,我们都必须提供一个元素拉链,这样很好,我们知道整个上下文都可以dXs返回到根,并且dY可以描述元素在其节点中的位置。对于每个X位置,都有一个进一步的子树可以探索,因此我们扩大了堆栈并继续前进!

那只剩下转移焦点的事了。我们可能会保持原状,或者从我们所在的位置走下,或者上升,或者上升然后再下降。开始。

  aroundF z@(MuCx {aboveY = dXs, belowY = dY} :<-: _) = MuCx
    {  aboveY = yOnUp dXs (In (up VY (zZipY z)))
    ,  belowY = contextualize dXs (cxZ $ around VY (zZipY z))
    }  :<-: z

与以往一样,现有元素被其整个拉链取代。对于这一belowY部分,我们看一下现有节点中还有哪些地方可以找到:我们将找到替代元素-positionsY或其他X-subnodes进行探索,因此我们将contextualise其找到。对于这一aboveY部分,我们必须X在重新组装我们要访问的节点之后,以自己的方式备份-derivatives堆栈。

yOnUp :: Diff2 b => [D b X (Mu b y) y] -> Mu b y ->
         [D b X (Mu b (ZF (Mu b) y)) (ZF (Mu b) y)]
yOnUp [] t = []
yOnUp (dX : dXs) (t :: Mu b y)
  =  contextualize dXs (cxZ $ around VX (dX :<- XX t))
  :  yOnUp dXs (In (up VX (dX :<- XX t)))

在此过程的每个步骤中,我们都可以转到around或继续前进。

就是这样!我没有给出法律的正式证明,但是在我看来,这些操作似乎在爬网结构时仔细地正确维护了上下文。

我们学到了什么?

可区分性引出了上下文中事物的概念,引入了一种共语结构,extract可以在其中为您提供事物并duplicate探索上下文以寻找其他事物以实现上下文。如果我们对节点具有适当的差分结构,则可以为整棵树开发差分结构。

哦,分别对待类型构造函数的每个Arity是公然的。更好的方法是在索引集之间使用函子

f :: (i -> *) -> (o -> *)

在这里我们可以创建o不同种类的结构,以存储i不同种类的元素。这些在雅可比构造下是封闭

J f :: (i -> *) -> ((o, i) -> *)

其中每个生成的-(o, i)结构都是偏导数,告诉您如何在i-o结构中制作-元素孔。但这又是一次有趣的打字。


2
使用“类型检查作为视频游戏”,或者说,仅对类型进行推理,我就越过了Comonad关卡,但只能达到另一个结局。在玩游戏时,我遇到了一个有趣而棘手的问题。类型检查器说,孔的类型是a -> a(对于某些大的长类型a),但是用孔填充id无效。问题是a ~ D t ~ D r,我实际上需要一个函数D r -> D t,并且需要为类型检查器提供的证明D r ~ D t
Cirdec

3
因此,当ghc用SPJ的声音说“不不不,我想!”时,请谨慎使用ScopedTypeVariables 。但是猜测太难了。
Pigworker 2014年

12
简短的答案似乎是Diff也需要around其签名。好长的答案是,像往常一样神奇地睁开眼睛。非常感谢您花一分钟时间编写本文!
J. Abrahamson,2014年

1
要插入件downaround是相同的。似乎我们应该能够通过诸如descend f (a :*: b) = pure (:*:) <*> f (InL . (:*: b)) a <*> f (InR . (a :*:)) bwheredescend沿的类型 来指定产品的两者Applicative (m t) => (forall f g. (Diff f, Diff g) => (D f a -> D g a) -> f a -> m g (f a)) -> t a -> m t (t a)
Cirdec

1
around可以完全用downup和第二个导数来编写,可以重复使用代码updown而无需像Applicative捕获它那样进行其他抽象。
Cirdec

12

Comonad拉链的实例不是

instance (Diff t, Diff (D t)) => Comonad (Zipper t) where
    extract = here
    duplicate = fmap outOf . inTo

实例本身的位置outOfinTo来源。以上情况触犯法律。相反,它的行为类似于:DiffZipper tComonadfmap extract . duplicate == id

fmap extract . duplicate == \z -> fmap (const (here z)) z

差异(拉链t)

通过将Diff实例Zipper标识为产品并重新使用产品代码(如下)来提供的实例。

-- Zippers are themselves products
toZipper :: (D t :*: Identity) a -> Zipper t a
toZipper (d :*: (Identity h)) = Zipper d h

fromZipper :: Zipper t a -> (D t :*: Identity) a
fromZipper (Zipper d h) = (d :*: (Identity h))

鉴于它们的衍生物之间的同构数据类型之间的同构,而且,我们可以重用一个类型的inTooutOf为其他。

inToFor' :: (Diff r) =>
            (forall a.   r a ->   t a) ->
            (forall a.   t a ->   r a) ->
            (forall a. D r a -> D t a) ->
            (forall a. D t a -> D r a) ->
            t a -> t (Zipper t a)
inToFor' to from toD fromD = to . fmap (onDiff toD) . inTo . from

outOfFor' :: (Diff r) =>
            (forall a.   r a ->   t a) ->
            (forall a.   t a ->   r a) ->
            (forall a. D r a -> D t a) ->
            (forall a. D t a -> D r a) ->
            Zipper t a -> t a
outOfFor' to from toD fromD = to . outOf . onDiff fromD

对于只是现有Diff实例的newTypes的类型,其派生类型是相同的类型。如果我们告诉类型检查器有关该类型相等性的信息D r ~ D t,则可以利用它,而不是为派生类提供同构。

inToFor :: (Diff r, D r ~ D t) =>
           (forall a. r a -> t a) ->
           (forall a. t a -> r a) ->
           t a -> t (Zipper t a)
inToFor to from = inToFor' to from id id

outOfFor :: (Diff r, D r ~ D t) =>
            (forall a. r a -> t a) ->
            (forall a. t a -> r a) ->
            Zipper t a -> t a
outOfFor to from = outOfFor' to from id id

配备了这些工具,我们可以将Diff实例重用于产品以实施Diff (Zipper t)

-- This requires undecidable instances, due to the need to take D (D t)
instance (Diff t, Diff (D t)) => Diff (Zipper t) where
    type D (Zipper t) = D ((D t) :*: Identity)
    -- inTo :: t        a -> t        (Zipper  t         a)
    -- inTo :: Zipper t a -> Zipper t (Zipper (Zipper t) a)
    inTo = inToFor toZipper fromZipper
    -- outOf :: Zipper  t         a -> t        a
    -- outOf :: Zipper (Zipper t) a -> Zipper t a
    outOf = outOfFor toZipper fromZipper

样板

为了实际使用此处提供的代码,我们需要一些语言扩展,导入以及对所提出问题的重述。

{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE RankNTypes #-}

import Control.Monad.Identity
import Data.Proxy
import Control.Comonad

data Zipper t a = Zipper { diff :: D t a, here :: a }

onDiff :: (D t a -> D u a) -> Zipper t a -> Zipper u a
onDiff f (Zipper d a) = Zipper (f d) a

deriving instance Diff t => Functor (Zipper t)
deriving instance (Eq (D t a), Eq a) => Eq (Zipper t a)
deriving instance (Show (D t a), Show a) => Show (Zipper t a)

class (Functor t, Functor (D t)) => Diff t where
  type D t :: * -> *
  inTo  :: t a -> t (Zipper t a)
  outOf :: Zipper t a -> t a

乘积,求和和常数

Diff (Zipper t)实例依赖于Difffor产品:*:,和:+:,常量Identity和零的实现Proxy

data (:+:) a b x = InL (a x) | InR (b x)
    deriving (Eq, Show)
data (:*:) a b x = a x :*: b x
    deriving (Eq, Show)

infixl 7 :*:
infixl 6 :+:

deriving instance (Functor a, Functor b) => Functor (a :*: b)

instance (Functor a, Functor b) => Functor (a :+: b) where
    fmap f (InL a) = InL . fmap f $ a
    fmap f (InR b) = InR . fmap f $ b


instance (Diff a, Diff b) => Diff (a :*: b) where
    type D (a :*: b) = D a :*: b :+: a :*: D b
    inTo (a :*: b) = 
        (fmap (onDiff (InL . (:*: b))) . inTo) a :*:
        (fmap (onDiff (InR . (a :*:))) . inTo) b
    outOf (Zipper (InL (a :*: b)) x) = (:*: b) . outOf . Zipper a $ x
    outOf (Zipper (InR (a :*: b)) x) = (a :*:) . outOf . Zipper b $ x

instance (Diff a, Diff b) => Diff (a :+: b) where
    type D (a :+: b) = D a :+: D b
    inTo (InL a) = InL . fmap (onDiff InL) . inTo $ a
    inTo (InR b) = InR . fmap (onDiff InR) . inTo $ b
    outOf (Zipper (InL a) x) = InL . outOf . Zipper a $ x
    outOf (Zipper (InR a) x) = InR . outOf . Zipper a $ x

instance Diff (Identity) where
    type D (Identity) = Proxy
    inTo = Identity . (Zipper Proxy) . runIdentity
    outOf = Identity . here

instance Diff (Proxy) where
    type D (Proxy) = Proxy
    inTo = const Proxy
    outOf = const Proxy

Bin示例

我把这个Bin例子看作是乘积之和的同构。我们不仅需要它的导数,还需要它的二阶导数

newtype Bin   a = Bin   {unBin   ::      (Bin :*: Identity :*: Bin :+: Identity)  a}
    deriving (Functor, Eq, Show)
newtype DBin  a = DBin  {unDBin  ::    D (Bin :*: Identity :*: Bin :+: Identity)  a}
    deriving (Functor, Eq, Show)
newtype DDBin a = DDBin {unDDBin :: D (D (Bin :*: Identity :*: Bin :+: Identity)) a}
    deriving (Functor, Eq, Show)

instance Diff Bin where
    type D Bin = DBin
    inTo  = inToFor'  Bin unBin DBin unDBin
    outOf = outOfFor' Bin unBin DBin unDBin

instance Diff DBin where
    type D DBin = DDBin
    inTo  = inToFor'  DBin unDBin DDBin unDDBin
    outOf = outOfFor' DBin unDBin DDBin unDDBin

上一个答案的示例数据是

aTree :: Bin Int    
aTree =
    (Bin . InL) (
        (Bin . InL) (
            (Bin . InR) (Identity 2)
            :*: (Identity 1) :*:
            (Bin . InR) (Identity 3)
        )
        :*: (Identity 0) :*:
        (Bin . InR) (Identity 4)
    )

不是Comonad实例

Bin上面的例子中提供了一个反例fmap outOf . inTo被正确执行的duplicateZipper t。特别是,它为fmap extract . duplicate = id法律提供了反例:

fmap ( \z -> (fmap extract . duplicate) z == z) . inTo $ aTree

评估结果为(注意,False到处都充满了,False只要足以证明法律是不正确的)

Bin {unBin = InL ((Bin {unBin = InL ((Bin {unBin = InR (Identity False)} :*: Identity False) :*: Bin {unBin = InR (Identity False)})} :*: Identity False) :*: Bin {unBin = InR (Identity False)})}

inTo aTree是一棵与的结构相同的树aTree,但是到处都有一个值,而是有一个带有该值的拉链,而其余的树则保留了所有原始值。fmap (fmap extract . duplicate) . inTo $ aTree也是一棵与相同结构的树aTree,但是每个都有一个值,而是一个带值的拉链,剩下的带有所有值的树的其余部分被相同的值代替。换一种说法:

fmap extract . duplicate == \z -> fmap (const (here z)) z

完整的测试套件的全部三个Comonad法律extract . duplicate == idfmap extract . duplicate == id以及duplicate . duplicate == fmap duplicate . duplicateIS

main = do
    putStrLn "fmap (\\z -> (extract . duplicate) z == z) . inTo $ aTree"
    print   . fmap ( \z -> (extract . duplicate) z == z) . inTo $ aTree    
    putStrLn ""
    putStrLn  "fmap (\\z -> (fmap extract . duplicate) z == z) . inTo $ aTree"
    print    . fmap ( \z -> (fmap extract . duplicate) z == z) . inTo $ aTree    
    putStrLn ""
    putStrLn "fmap (\\z -> (duplicate . duplicate) z) == (fmap duplicate . duplicate) z) . inTo $ aTree"
    print   . fmap ( \z -> (duplicate . duplicate) z == (fmap duplicate . duplicate) z) . inTo $ aTree

1
updownConal博客中的into和相同outof
J. Abrahamson,2014年

我可以看到@pigworker尝试沿着一年前尝试的相同路径前进。stackoverflow.com/questions/14133121/...
Cirdec

8

给定一个无限可微Diff类:

class (Functor t, Functor (D t)) => Diff t where
    type D t :: * -> *
    up :: Zipper t a -> t a
    down :: t a -> t (Zipper t a)  
    -- Require that types be infinitely differentiable
    ddiff :: p t -> Dict (Diff (D t))

around可以写成的术语updownZipperdiff的derivitive,基本上如

around z@(Zipper d h) = Zipper ctx z
    where
        ctx = fmap (\z' -> Zipper (up z') (here z')) (down d)

Zipper t a由一个D t aa。我们去downD t aD t (Zipper (D t) a)在每个孔中都装有一个拉链。那些拉链由aD (D t) aa位于孔中的组成。我们去up他们每个人,得到一个D t a并与a那个洞中的那个去配对。AD t aamake a Zipper t a,给我们a D t (Zipper t a),这是a所需的上下文Zipper t (Zipper t a)

Comonad实例然后简单地

instance Diff t => Comonad (Zipper t) where
    extract   = here
    duplicate = around

捕获导数的Diff字典需要一些额外的检查,可以使用Data.Constraint或根据相关答案中提供的方法来完成

around :: Diff t => Zipper t a -> Zipper t (Zipper t a)
around z = Zipper (withDict d' (fmap (\z' -> Zipper (up z') (here z')) (down (diff z)))) z
    where
        d' = ddiff . p' $ z
        p' :: Zipper t x -> Proxy t
        p' = const Proxy 

愚弄这个似乎效果很好:gist.github.com/tel/fae4f90f47a9eda0373b。我会很整齐地看看是否可以将自定义拉链拉到地面,然后使用它来获取自动arounds。
J. Abrahamson 2014年

2
第一个around也使用around :: (Diff t, Diff (D t)) => Zipper t a -> Zipper t (Zipper t a)和没有ddiff方法进行类型检查,对于Comonad实例也是如此,因此两次可区分性似乎就足够了。
与Orjan约翰森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.