伴随仿函数确定monad变压器,但哪里升力?

时间:2018-03-16 13:45:36

标签: haskell monads monad-transformers state-monad

我对描述here的构造感兴趣,因为它确定了来自伴随函子的monad变换器。这里有一些代码总结了基本概念:

{-# LANGUAGE MultiParamTypeClasses #-}

import           Control.Monad

newtype Three g f m a = Three { getThree :: g (m (f a)) }

class (Functor f, Functor g) => Adjoint f g where
  counit :: f (g a) -> a
  unit   :: a -> g (f a)

instance (Adjoint f g, Monad m) => Monad (Three g f m) where
  return  = Three . fmap return . unit
  m >>= f = Three $ fmap (>>= counit . fmap (getThree . f)) (getThree m)

instance (Adjoint f g, Monad m) => Applicative (Three g f m) where
  pure  = return
  (<*>) = ap

instance (Adjoint f g, Monad m) => Functor (Three g f m) where
  fmap = (<*>) . pure

鉴于Adjoint ((,) s) ((->) s)Three ((->) s) ((,) s)似乎等同于StateT s

非常酷,但我对一些事情感到困惑:

  • 如何将monadic m a升级为monadic Three g f m a?对于Three ((->) s) ((,) s)的特定情况,它当然很明显如何执行此操作,但似乎需要一个适用于Three g f Adjoint f g的任何lift的配方。换句话说,似乎应该有unit的模拟,其定义只需要counitreturn以及输入的>>=f单子。但我似乎找不到一个(我看过a definition using sequence,但这似乎有点像作弊,因为它需要Traversableg a

  • 就此而言,我们如何将Three g f m a升级为Adjoint f g(提供Three ((->) s) ((,) s))?同样,对于gets的特定情况,显而易见的是如何做到这一点,但我想知道是否有unit的模拟只需要{{1} }},counit以及输入monad的return>>=

2 个答案:

答案 0 :(得分:3)

  

我们如何将monadic m a升级为monadic Three g f m a

好问题。打网球的时间!

-- i'm using Adjuction from the adjunctions package because I'll need the fundeps soon
lift :: Adjunction f g => m a -> Three g f m a
lift mx = Three _

洞的类型为g (m (f a))。我们在范围内mx :: m a,当然还有unit :: a -> g (f a)fmap :: (a -> b) -> m a -> m b

lift mx = let mgfx = fmap unit mx
          in Three $ _ mgfx

现在它是_ :: m (g (f a)) -> g (m (f a))。如果gdistribute,则为Distributive

lift mx = let mgfx = fmap unit mx
              gmfx = distributeR mgfx
          in Three gmfx
-- or
lift = Three . distributeR . fmap unit

所以现在我们只需要证明一个附属的右边总是Distributive

distributeR :: (Functor m, Adjunction f g) => m (g x) -> g (m x)
distributeR mgx = _

由于我们需要返回g,因此Adjunction明确选择方法为leftAdjunct :: Adjunction f g => (f a -> b) -> a -> g b,使用unit创建g (f a),然后通过f a ping函数来删除内部fmap

distributeR mgx = leftAdjunct (\fa -> _) _

我要先攻击第一洞,期望填补它可能会告诉我一些关于第二洞的事情。第一个洞的类型为m a。我们唯一可以获得任何类型m的方法是fmap ping mgx以上的内容。

distributeR mgx = leftAdjunct (\fa -> fmap (\gx -> _) mgx) _

现在第一个洞的类型为a,我们的范围为gx :: g a。如果我们有f (g a),我们可以使用counit。但我们确实有一个f x(其中x目前是一个模糊的类型变量)和范围内的g a

distributeR mgx = leftAdjunct (\fa -> fmap (\gx -> counit (fa $> gx)) mgx) _

事实证明,剩下的洞有一个模糊的类型,所以我们可以使用我们想要的任何东西。 ($>将忽略它。)

distributeR mgx = leftAdjunct (\fa -> fmap (\gx -> counit (fa $> gx)) mgx) ()

这种衍生可能看起来像一个魔术,但实际上你通过练习在打网球方面变得更好。游戏的技巧是能够查看类型并应用有关您正在使用的对象的直觉和事实。从查看类型我可以告诉我需要交换mg,并且遍历m不是一个选项(因为m不一定是{ {1}}),所以Traversable之类的东西是必要的。

除了猜测我需要实施distribute之外,我还有一些关于结合如何工作的一般知识的指导。

具体而言,当您谈论distribute时,唯一有趣的附加内容(唯一同构)* -> * / Reader附加内容。特别是,这意味着Writer上的任何正确伴随始终是Representable,由tabulateAdjunctionindexAdjunction见证。我也知道所有Hask个仿函数都是Representable(实际上逻辑上反过来也是如此,如Distributive's docs所述,即使这些类的功效不相同),每distributeRep

  

就此而言,我们如何将Distributive升级为g a(提供Three g f m a)?

我将此作为练习。我怀疑你需要再次依靠Adjoint f g同构。我实际上并不希望这一个对所有附加都是正确的,只有g ~ ((->) s)上的那些,其中只有一个。

答案 1 :(得分:2)

Benjamin Hodgson's answer中的

lift设置为:

lift mx = let mgfx = fmap unit mx
              gmfx = distributeR mgfx
          in Three gmfx
-- or
lift = Three . distributeR . fmap unit

如您所知,这不是我们可能在那里使用的唯一合理策略:

lift mx = let gfmx = unit mx
              gmfx = fmap sequenceL gfmx
          in Three gmfx
-- or
lift = Three . fmap sequenceL . unit

TraversableEdward Kmett's corresponding MonadTrans instance的要求始于何时。那么,问题就在于,依赖于那个,就像你说的那样,&#34;作弊&#34;。我认为它不是。

我们可以调整本杰明关于Distributive和右边界的游戏计划,并尝试查找左边邻居是否为Traversable。查看Data.Functor.Adjunction表明我们有一个非常好的工具箱可供使用:

unabsurdL :: Adjunction f u => f Void -> Void
cozipL :: Adjunction f u => f (Either a b) -> Either (f a) (f b)
splitL :: Adjunction f u => f a -> (a, f ())
unsplitL :: Functor f => a -> f () -> f a

爱德华有用地告诉我们unabsurdLcozipL见证&#34; [a]左伴随必须有人居住,[并且]左伴随必须由一个元素居住&#34 ;, 分别。但是,这意味着splitL精确对应于表征Traversable仿函数的形状和内容分解。如果我们添加splitLunsplitL是倒置的事实,则会立即执行sequence

sequenceL :: (Adjunction f u, Functor m) => f (m a) -> m (f a)
sequenceL = (\(mx, fu) -> fmap (\x -> unsplitL x fu) mx) . splitL

(请注意,Functor要求m不超过lift,正如对于只包含一个值的可遍历容器所预期的那样。)

此时缺少的是验证distributeR的两个实现都是等效的。这并不困难,只是有点费力。简而言之,此处的sequenceRdistributeR = \mgx -> leftAdjunct (\fa -> fmap (\gx -> rightAdjunct (const gx) fa) mgx) () sequenceL = rightAdjunct (\mx -> leftAdjunct (\fu -> fmap (\x -> fmap (const x) fu) mx) ()) 定义可以简化为:

distributeR . fmap unit = fmap sequenceL . unit

我们希望显示distributeR . fmap unit = \mgx -> leftAdjunct (\fa -> fmap (\gx -> rightAdjunct (const (unit gx)) fa) mgx) () fmap sequenceL . unit = \mx -> leftAdjunct (\fu -> fmap (\x -> fmap (const x) fu) mx) () 。经过几轮简化后,我们得到:

\fu -> fmap (\x -> fmap (const x) fu) mx

我们可以通过选择leftAdjunct - 第二个右侧rightAdjunct unit = counit . fmap unit = id的参数 - 并将\fu -> fmap (\x -> fmap (const x) fu) mx \fu -> fmap (\x -> fmap (const x) fu) mx \fu -> fmap (\x -> (counit . fmap unit . fmap (const x)) fu) mx \fu -> fmap (\x -> rightAdjunct (unit . const x) fu) mx \fu -> fmap (\x -> rightAdjunct (const (unit x)) fu) mx -- Sans variable renaming, the same as -- \fa -> fmap (\gx -> rightAdjunct (const (unit gx)) fa) mgx 滑入其中来显示这些内容真的相同:

Traversable

需要注意的是,MonadTrans路径DistributiveControl.Monad.Trans.Adjoint路径一样安全,并且对此感到担忧 - 包括lift提到的路线文档 - 不再麻烦任何人。

P.S。:值得注意的是,这里提出的lift = Three . leftAdjunct sequenceL 的定义可以拼写为:

lift

也就是说,sequenceL是通过附录同构发送的leftAdjunct sequenceL = distributeR . fmap unit 。另外,来自......

rightAdjunct

...如果我们双方都申请sequenceL = rightAdjunct (distributeR . fmap unit) ,我们就会......

fmap (fmap counit)

......如果我们在双方左侧撰写distributeR = leftAdjunct (fmap counit . sequenceL) ,我们最终会得到:

distributeR

因此sequenceLvector<vector<Edge> >graph是可以相互确定的。