5

我们可以将 monad 描述为计算上下文,并且 monad 的实现完全保留了该上下文的含义。例如 Option - 上下文含义是该值可能存在。给定 Option 数据类型,唯一有意义的实现是pure = some, flatMap f = {none => none; some x => f x } 根据我对 monad 的理解,遵循类型签名 - 对于任何 monad,只有一个合理的实现。换句话说,如果你想为值/计算添加一些有意义的上下文,对于任何特定的 monad 只有一种方法可以做到这一点。
另一方面,当谈到comonad 时,它突然开始感到完全陌生,好像有很多方法可以为给定类型实现comonad,你甚至可以为每个实现赋予一定的含义。
考虑一下,NEL,与copure = headcojoin是通过实现tails的,完美满足了类型。如果我们cojoin通过permutations或因为fa map (_ => fa) map f它不满足comonad 法则来实施。
但是循环实现是有效的:

override def cobind[A, B](fa: NonEmptyList[A])(f: (NonEmptyList[A]) => B): NonEmptyList[B] = {
  val n: NonEmptyList[NonEmptyList[A]] = fa.map(_ => fa).zipWithIndex.map { case (li , i ) =>
    val(h: List[A], t: List[A]) = li.list.splitAt(i)
    val ll: List[A] = t ++ h
    NonEmptyList.nel(ll.head, ll.tail)
  }
  n map f
}

Command 如此模棱两可的原因,即使有法律限制我们,正如我所见,如果在 Monad 我们在某些情况下限制自己(我们不能“创造”新信息),在 Comonad,我们是进一步扩展该上下文(有很多方法可以从列表中创建列表列表),这为我们提供了更多的可能性。在我的脑海中比喻是:对于 Monad,我们站在路上,想要达到一些目的地点 A = 因此只有有意义的最短路径可供选择。在命令中,我们站在 A 中,想从那里去某个地方,所以有更多的方法可以做到这一点。
所以我的问题是 - 我真的对吗?我们可以用不同的方式实现命令,每次都做另一个有意义的抽象吗?或者只是尾巴实施是合理的,因为 comonad 应该引入抽象。

4

2 回答 2

8

非空列表作为两个不同的comonads 出现在两个标准结构中。

首先,cofree 共单子是这样给出的。

data Cofree f x = x :& f (Cofree f x)  -- every node is labelled with an x

instance Functor f => Functor (Cofree f) where
  fmap f (x :& fcx) = f x :& fmap (fmap f) fcx

instance Functor f => Comonad (Cofree f) where
  extract (x :& _) = x   -- get the label of the top node
  duplicate cx@(_ :& fcx) = cx :& fmap duplicate fcx

非空列表可以表示为

type Nellist1 = Cofree Maybe

并且因此是自动共联的。这给了你“尾巴”comonad。

同时,将结构分解为“元素拉链”会产生共元结构。正如我详细解释的那样

可微性相当于拉链上的这一系列操作(从上下文中挑选出的单个元素并“聚焦”)

class (Functor f, Functor (DF f)) => Diff1 f where
  type DF f :: * -> *
  upF      ::  ZF f x  ->  f x           -- defocus
  downF    ::  f x     ->  f (ZF f x)    -- find all ways to focus
  aroundF  ::  ZF f x  ->  ZF f (ZF f x) -- find all ways to *re*focus

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

所以我们得到一个函子和一个comonad

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

原则上,这种结构也会产生非空列表。问题是被微分的函子在 Haskell 中并不那么容易表达,尽管导数是合理的。让我们发疯吧...

非空列表相当于ZF thingy xwhere DF thingy = []。我们可以整合列表吗?在代数上愚弄可能会给我们一个线索

[x] = Either () (x, [x]) = 1 + x * [x]

所以作为幂级数,我们得到

[x] = Sum(n :: Nat). x^n

我们可以整合电源系列

Integral [x] dx = Sum(n :: Nat). x^(n+1)/(n+1)

这意味着我们得到了某种大小为 (n+1) 的任意元组,但我们必须将它们识别到等价类的大小为 (n+1) 的某种关系。一种方法是识别到旋转的元组,因此您不知道 (n+1) 个位置中的哪个是“第一个”。

也就是说,列表是非空循环的导数。想想一群人在圆桌旁打牌(可能是纸牌)。旋转桌子,你会得到同样的一群人打牌。但是一旦你指定了庄家,你就可以按照顺时针从庄家左边开始按顺序确定其他玩家的名单。

两种标准结构;同一个函子的两个共子。

(在我之前的评论中,我谈到了多个 monad 的可能性。这有点涉及,但这是一个起点。每个 monadm也是 applicative,applicative 定律构成m ()一个 monoid。相应地,每个 monoid 结构m ()至少给出一个monad 结构的候选对象m。在 writer monads 的情况下(,) s,我们得到 monads 的候选对象是与 on 上的 monoids(s,())相同的 monoids s。)

编辑非空列表也至少以两种不同的方式是一元的。

我定义了仿函数的身份和配对,如下所示。

newtype I         x = I x
data    (f :*: g) x = (:&:) {lll :: f x, rrr :: g x}

现在,我可以如下引入非空列表,然后定义连接。

newtype Ne x = Ne ((I :*: []) x)

cat :: Ne x -> Ne x -> Ne x
cat (Ne (I x :&: xs)) (Ne (I y :&: ys)) = Ne (I x :&: (xs ++ y : ys))

这些是一元的,就像可能的空列表一样:

instance Monad Ne where
  return x = Ne (I x :&: [])
  Ne (I x :&: xs) >>= k = foldl cat (k x) (map k xs)

然而,I是一个单子:

instance Monad I where
  return = I
  I a >>= k = k a

此外,单子在配对下是封闭的:

instance (Monad f, Monad g) => Monad (f :*: g) where
  return x = return x :&: return x
  (fa :&: ga) >>= k = (fa >>= (lll . k)) :&: (ga >>= (rrr . k))

所以我们可以写

newtype Ne x = Ne ((I :*: []) x) deriving (Monad, Applicative, Functor)

但是returnfor 那个 monad 给了我们双重的视野。

return x = Ne (I x :&: [x])

所以你有:非空列表是comonadic 两种方式,monadic 两种方式,applicative 六种方式,......

(关于这一点还有很多话要说,但我必须在某个地方停下来。)

于 2016-03-04T23:11:51.840 回答
1

这是相同的反例,显示 Monad 有多个可能的实例,来自 pigworker 的评论,但更多的是通过(虽然没有经过类型检查,所以请原谅任何错误)。

data WithBool a = WB Bool a deriving Functor

instance Monad WithBool where
     return = WB z
     (WithBool b a) >>= f =
           case f a of (WithBool b2 r) -> WithBool (b `op` b2) r

-- This holds if op = (&&) and z = True
-- This also holds if op = (||) and z = False
-- It should also work if op = const or `flip const` and z = True _or_ False

正如 Bakuriu 所说,“默认”实现的选择因此有些武断,并取决于人们预期的需求。

于 2016-03-04T18:24:54.140 回答