Zipper Comonads, Generically

前端 未结 3 1961
既然无缘
既然无缘 2020-11-28 01:06

Given any container type we can form the (element-focused) Zipper and know that this structure is a Comonad. This was recently explored in wonderful detail in another Stack

3条回答
  •  眼角桃花
    2020-11-28 02:00

    The Comonad instance for zippers is not

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

    where outOf and inTo come from the Diff instance for Zipper t itself. The above instance violates the Comonad law fmap extract . duplicate == id. Instead it behaves like:

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

    Diff (Zipper t)

    The Diff instance for Zipper is provided by identifying them as products and reusing the code for products (below).

    -- 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))
    

    Given an isomorphism between data types, and an isomorphism between their derivatives, we can reuse one type's inTo and outOf for the other.

    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
    

    For types that are just newTypes for an existing Diff instance, their derivatives are the same type. If we tell the type checker about that type equality D r ~ D t, we can take advantage of that instead of providing an isomorphism for the derivatives.

    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
    

    Equipped with these tools, we can reuse the Diff instance for products to implement 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
    

    Boilerplate

    In order to actually use the code presented here, we need some language extensions, imports, and a restatement of the proposed problem.

    {-# 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
    

    Products, Sums, and Constants

    The Diff (Zipper t) instance relies on implementations of Diff for products :*:, sums :+:, constants Identity, and zero 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 Example

    I posed the Bin example as an isomorphism to a sum of products. We need not only its derivative but its second derivative as well

    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
    

    The example data from the previous answer is

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

    Not the Comonad instance

    The Bin example above provides a counter-example to fmap outOf . inTo being the correct implementation of duplicate for Zipper t. In particular, it provides a counter-example to the fmap extract . duplicate = id law:

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

    Which evaluates to (notice how it is full of Falses everywhere, any False would be enough to disprove the law)

    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 is a tree with the same structure as aTree, but everywhere there was a value there is instead a zipper with the value, and the remainder of the tree with all of the original values intact. fmap (fmap extract . duplicate) . inTo $ aTree is also a tree with the same structure as aTree, but everywere there was a value there is instead a zipper with the value, and the remainder of the tree with all of the values replaced with that same value. In other words:

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

    The complete test-suite for all three Comonad laws, extract . duplicate == id, fmap extract . duplicate == id, and duplicate . duplicate == fmap duplicate . duplicate is

    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
    

提交回复
热议问题