合理的Comonad实施

时间:2016-03-04 09:30:26

标签: haskell functional-programming scalaz comonad

我们可以将monad描述为计算上下文,monad实现完全保留了该上下文的含义。 例如Option - 上下文含义是值可能存在。 给定Option数据类型,唯一有意义的实现是pure = some, flatMap f = {none => none; some x => f x } 正如我对monad的理解,通过遵循类型签名 - 对于任何monad只有一个合理的实现。换句话说,如果要为值/计算添加一些有意义的上下文,则只有一种方法可以为任何特定的monad执行此操作。
另一方面,当涉及到comonad时,它突然开始感觉完全奇怪,就像有很多方法来实现给定类型的comonad,你甚至可能为每个实现赋予一定的意义。
NEL考虑copure = headcojoin通过tails实现,完全满足类型。如果我们按cojoinpermutations实施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,并想从它去某个地方,所以有更多的方法来做它。
所以我的问题是 - 我真的对吗?我们可以用不同的方式实现命令,每次都进行另一个有意义的抽象吗?或者只有 tails 实现是合理的,因为comonad假设引入了抽象。

2 个答案:

答案 0 :(得分:7)

非空列表由两个标准结构作为两个不同的comonad出现。

首先,这样给出了 cofree comonad

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

因此自动成为comonadic。这给了你"尾巴" comonad。

同时,将结构分解为"元素拉链"诱导共同结构。正如我explained at great length

可分性相当于拉链上的这一系列操作(从他们的上下文中挑选出来的单个元素,并且#34;聚焦")

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 x 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)个位置是&#34;第一个&#34;。

也就是说,列表是非空循环的衍生物。想想在圆桌上打牌(可能是单人纸牌)的一群人。旋转桌子,你会得到同样的一堆人打牌。但是一旦你指定了经销商,你就可以按顺序从经销商的左边顺序修复其他玩家的名单。

两种标准结构;同一个仿函数的两个comonads。

(在我之前的评论中,我评论了多个monad的可能性。它有点涉及,但这里是一个起点。每个monad m也适用,并且适用法律使m ()成为一个幺半群。相应地,m ()的每个幺半群结构至少为m上的monad结构提供候选。对于作家monads (,) s,我们得到monad的候选人是(s,())上的幺半群,与s上的幺半群相同。)

编辑非空列表至少还有两种截然不同的 monadic

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

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

这些是monadic,就像空列表一样:

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

但是,I是monad:

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

此外,monad在配对下关闭:

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)

但该单子的return给了我们双重视力。

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

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

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

答案 1 :(得分:1)

这是一个相同的反例,显示Monad有多个可能的实例,来自pigworker的评论,但更多的工作(虽然不是typechecked,所以原谅任何错误)。

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所说,“默认”实施的选择在某种程度上是任意的,并且取决于人们期望的内容。