什么是Cocartesian comonoid,什么是Cocartesian comonoidal函子?

发布于 2025-02-05 23:46:37 字数 252 浏览 2 评论 0 原文

我最近一直在尝试使用Monoid和分销剂,我认为我发现了一些有趣的东西(在答案中描述了) - 这些已经是已知的结构吗? (我一直无法在线找到对它们的任何参考,但我不认为我错过了一些原因,它们会毫无意义

)给任何不是我的人?

引导我这里的问题是:

  1. 如果您将产品交换为comonoids中的总和会发生什么?
  2. 如果您将产品交换为共同函数的总和(共同涂抹剂,煤层,代码可见,编码)会发生什么? (仅在前两个回答)

I've been experimenting with monoids and Distributives lately, and I think I've found something of interest (described in my answer) - are these already known structures? (I've been unable to find any reference to them online, and I don't think I've missed some reason they would be nonsensical)

If not previously known, do they seem useful, or interesting to anyone who isn't me?

The questions that lead me here are:

  1. What happens if you swap products for sums in comonoids?
  2. What happens if you swap products for sums in comonoidal functors (coapplicatives, coalternatives, codivisibles, codecidables)? (Only somewhat answered for the first two)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不离久伴 2025-02-12 23:46:37

以下是这些结构背后的定律,因为它们是最新的实验产物。

首先,Cocartesian comagma,以及身份:

-- modified to an equivalent definition for clarity
class Semigroup a where
    (<>) :: (a,a) -> a
class Semigroup a => Monoid a where
    mempty :: () -> a

class Split a where
    split :: a -> Either a a
class Idsplit a where
    void :: a -> Void

这些是具有固有分支能力的数据类型 - 成为合适的comonoid,此分支不应改变其值(由于功能的整洁Typeclass实例),但这会导致较小的有趣的类型用于此处描述的目的。

以下是函数的实例,与OP的单次征用实例相对应:

instance Split r => Alt ((->) r) where
    (<!>) :: (r -> a) -> (r -> a) -> (r -> a)
    f1 <!> f2 = either f1 f2 . split
instance Idsplit r => Alternative ((->) r) where
    (<|>) = (<!>)
    empty = absurd . void

不幸的是,除非合法分裂,否则这不会是关联的 - 但是我认为它的非社交形式仍然可以使用吗?

也可以定义一个可展开的类型类似于单型的可折叠类型,半群的可折叠1及其理论上的进一步家庭成员:

class Unfoldable l where
    unfoldMap :: Split m => (m -> e) -> (m -> l e)
instance Unfoldable [] where
    unfoldMap strat root = case strat root of
        Left m -> []
        Right m -> m : unfoldMap strat root

newtype Unfolder a = Unfolder { getUnfolder :: (a, a -> Maybe a) }
instance Split (Unfolder a) where
    
unfoldr :: (a -> Maybe (e,a)) -> (a -> [e])
unfoldr strat root = unfoldMap
    (fst . fst . getUnfolder)
    (Unfolder ((undefined, root), (strat . snd)))
-- uses Unfolder (e,a) similar to Endo a in foldr
-- the undefined will be dropped by list's instance of Unfoldable, and so is safe

下一:我认为是“ cocartesian cocatesian colternative functor”:

class Functor f => Match f where
    matchWith :: (c -> Either a b) -> f c -> Either (f a) (f b)
class Match f => Matchable f where
    voidWith :: (a -> Void) -> f a -> Void

-- Maybe is a Match, but not a Matchable due to Nothing
instance Match Maybe where
    matchWith _ Nothing = Left Nothing
    matchWith choose (Just a) = bimap Just Just $ choose a
-- this won't work
instance Matchable Maybe where
    voidWith void (Just a) = void a
    voidWith void Nothing = ?????

-- Pick always needs an a value, and so is Matchable as well
data Pick a = Pick1 a | Pick2 a
    deriving Functor
instance Match Pick where
    matchWith choose (Pick1 a) = bimap Pick1 Pick1 $ choose a
    matchWith choose (Pick2 a) = bimap Pick2 Pick2 $ choose a
instance Matchable Pick where
    voidWith void (Pick1 a) = void a
    voidWith void (Pick2 a) = void a

用于代数数据类型每个构造函数中的一个值(然后可以观察并匹配模式)。

可匹配的描述每个构造函数中具有一个值的函子(因此,无人居住的值会导致无人居住的函子)。

我认为可匹配的严格较弱比与函子而不是应用程序遍历的可遍历的类型类似,但尚未证明这一点 - 这与所有适用的分发剂相对应。 (每个构造函数中完全具有一个参数值的代数数据类型既可以匹配又可以遍历。)

class Functor l => FTraversable l where
    ftraverse :: Functor f => (a -> f b) -> (l a -> f (l b))

instance FTraversable f => Match f where
    matchWith choose fc = 
        let fab = fmap choose fc :: f (Either a b)
            afb = fsequence fab :: Either a (f b)
            bfa = fsequence (fmap swap fab) :: Either b (f a)
        in  case (afb, bfa) of
                (Left a, Right (f a)) -> Left (f a)
                (Right (f b), Left b) -> Right (f b)
                (_, _) -> undefined -- impossible?

instance FTraversable f => Matchable f where
    voidWith void fa = (absurd1 :: forall a. V1 a -> Void) . ftraverse ((absurd :: Void -> V1 ()) . void) $ fa

instance Matchable l => FTraversable l where
    ftraverse strat la = ???

对我来说,Motalables似乎很有趣,是我无法找到任何参考的扩展应用家庭的一部分,但它们确实会进入他们自己的“分布式”库(遍布双重)的分布函数。

正常的发行剂是读取器r 的同构,并且是著名的(我是著名的,至少是吗?这似乎是众所周知)等于代表函数,或者在Hask中的右伴随。它们是针对代数数据类型的解释,它们是具有一个构造函数的代数数据类型。

但是,这些可以扩展到基于函数的分布之外!

-- all defined using cotraverse instead of distribute, for clarity
-- (which is equivalent to using distribute)

-- isomorphic to Reader r (f a) for some r and Matchable f, not sure which
-- for algebraic datatypes, those with a finite constructor count
class Functor l => MatchableDistributive l where
    cotraverseMatchable :: Matchable f => (f a -> b) -> (f (l a) -> l b)

-- isomorphic to Reader r (f a) for some r and Match f, not sure which
-- for algebraic datatypes, those with a finite non-zero constructor count
class MatchableDistributive l => MatchDistributive l where
    cotraverseMatch :: Match f => (f a -> b) -> (f (l a) -> l b)

-- isomorphic to Reader r a for some r ~ Rep l
-- for algebraic datatypes, those with exactly one constructor
class MatchDistributive l => Distributive l where
    cotraverse :: Functor f => (f a -> b) -> (f (l a) -> l b)

这些镜像可遍历〜(l(),[a]),traversable1〜 (l(),nonepty a)和备用的函数 - 函数 - 可转换〜(l(),a)

(感兴趣:对于代数数据类型,每个可穿越的家庭成员具有与分布式等效物具有构造函数一样多的记录,反之亦然)

(有趣的是:就像共同应用在Hask中是微不足道的一样,我希望可以解释这一点 - 我希望这可以解释作为启用许多分布式和comatchables的记录,可以使许多可遍布的构造函数?)

匹配物也像定义通用实例的应用一样,除了它具有具有独特应用程序实例的应用程序的产品,但实际上是具有多种匹配物的总和一个独特的实例!

Cocartesian共同应用充当替代性的等效物-Cocartesian共同涂抹可以解释为能够在“ Unzip”操作中选择哪一方,而Cocartesian Coapplicative描述了一个完全不habf的函数,如v1中的v1。

class Functor f => Bias f where
    biasWith :: (c -> (a,b)) -> f c -> Either (f a) (f b)
class Bias f => Biasable f where
    devoidWith :: (a -> ()) -> (f a -> Void)

-- empty analogue
devoid :: Biasable f => f a -> Void
devoid = devoidWith (const ())

-- (<|>) analogue
biasing :: Bias f => f a -> Either (f a) (f a)
biasing = biasWith (\a -> (a,a))

总而言之:

  • 这里有4种类型的单体家族(Monoid,Comonoid,Cocartesian Monoid,Cocartesian comonoid),其中第一个和最后一个在Hask中是不个性的。如果您包括这些以及(),也许有6个?

  • 这里有6个应用家庭的成员(应用,替代性,可分割,可决定性,可匹配,有偏见),加上琐碎的共同应用且可梳理 - 可能会进一步填写至16名成员!或36包括上述

  • 可匹配的函子启用较弱的分布式版本 - 如果始终存在分布式数据,则有时仅存在匹配分发中的数据,或者可能永远不会在可匹配的分布

    中出现。

(这些适用的=从'semialign''软件包中对齐,但是在Ziplist的类型类实例中看到的与应用程序和拉链之间的关系相对应的法律较少?)

:一个更对称的列表实例

编辑 类似于单质型的类型类似的方法。

class MonoUnfoldable l e | l -> e where
    unfoldMapMono :: Split m => (m -> e) -> (m -> l)
-- this will always create an infinite list
instance MonoUnfoldable [Either a a] (Either a a) where
    unfoldMapMono strat m = strat m : unfoldMapMono strat m

pickLefts, pickRights :: [Either a a] -> [a]
pickLefts (Left a : as) = a : pickLefts as
pickLefts _ = []
pickRights (Right a : as) = a : pickRights as
pickRights _ = []

但是,进行展开的有用实例将是价值变化的,并且已经为这些价值变化选择了左/右偏置。例如:

instance Num n => Split (Sum n) where
    split (Sum n)
        | n > 0     = Right $ Sum (n-1)
        | otherwise = Left  $ Sum (n-1)

unfoldMap getSum (Sum 10) = [9,8,7,6,5,4,3,2,1,0]

因此,不对称实际上是该过程固有的,而所需的一切就是要保持左或右信号的终点,以表明列表状(线性,有限)的端点。

The following is fairly light on the laws behind these structures, as they are a recent product of experimentation.

First, the cocartesian comagma, plus identity:

-- modified to an equivalent definition for clarity
class Semigroup a where
    (<>) :: (a,a) -> a
class Semigroup a => Monoid a where
    mempty :: () -> a

class Split a where
    split :: a -> Either a a
class Idsplit a where
    void :: a -> Void

These are datatypes with an inherent ability to branch - to be a proper comonoid, this branching should not change its value (due to a neat typeclass instance for functions), but that leads to a far less interesting typeclass for the purposes described here.

Here's that instance for functions, corresponding to the monoid-requiring Divisible instance for Op:

instance Split r => Alt ((->) r) where
    (<!>) :: (r -> a) -> (r -> a) -> (r -> a)
    f1 <!> f2 = either f1 f2 . split
instance Idsplit r => Alternative ((->) r) where
    (<|>) = (<!>)
    empty = absurd . void

This will not be associative unless Split is lawful, unfortunately - but I think its non-associative form could still be of use?

It is also possible to define an Unfoldable typeclass, similar to Foldable for monoids, Foldable1 for semigroups, and their theoretical further family members:

class Unfoldable l where
    unfoldMap :: Split m => (m -> e) -> (m -> l e)
instance Unfoldable [] where
    unfoldMap strat root = case strat root of
        Left m -> []
        Right m -> m : unfoldMap strat root

newtype Unfolder a = Unfolder { getUnfolder :: (a, a -> Maybe a) }
instance Split (Unfolder a) where
    
unfoldr :: (a -> Maybe (e,a)) -> (a -> [e])
unfoldr strat root = unfoldMap
    (fst . fst . getUnfolder)
    (Unfolder ((undefined, root), (strat . snd)))
-- uses Unfolder (e,a) similar to Endo a in foldr
-- the undefined will be dropped by list's instance of Unfoldable, and so is safe

Next: what I think is a "cocartesian coalternative functor":

class Functor f => Match f where
    matchWith :: (c -> Either a b) -> f c -> Either (f a) (f b)
class Match f => Matchable f where
    voidWith :: (a -> Void) -> f a -> Void

-- Maybe is a Match, but not a Matchable due to Nothing
instance Match Maybe where
    matchWith _ Nothing = Left Nothing
    matchWith choose (Just a) = bimap Just Just $ choose a
-- this won't work
instance Matchable Maybe where
    voidWith void (Just a) = void a
    voidWith void Nothing = ?????

-- Pick always needs an a value, and so is Matchable as well
data Pick a = Pick1 a | Pick2 a
    deriving Functor
instance Match Pick where
    matchWith choose (Pick1 a) = bimap Pick1 Pick1 $ choose a
    matchWith choose (Pick2 a) = bimap Pick2 Pick2 $ choose a
instance Matchable Pick where
    voidWith void (Pick1 a) = void a
    voidWith void (Pick2 a) = void a

For algebraic datatypes, Match describes functors with at most one value in each constructor (which can then be observed and pattern-matched over).

Matchable describes functors with exactly one value in each constructor (so an uninhabited value leads to an uninhabited functor).

I believe Matchable is strictly weaker than a Traversable typeclass that traverses with Functors instead of Applicatives, but have not proven this - this would correspond to all Distributives being Applicative. (All algebraic datatypes with exactly one parameter value in each constructor are both Matchable and Traversable.)

class Functor l => FTraversable l where
    ftraverse :: Functor f => (a -> f b) -> (l a -> f (l b))

instance FTraversable f => Match f where
    matchWith choose fc = 
        let fab = fmap choose fc :: f (Either a b)
            afb = fsequence fab :: Either a (f b)
            bfa = fsequence (fmap swap fab) :: Either b (f a)
        in  case (afb, bfa) of
                (Left a, Right (f a)) -> Left (f a)
                (Right (f b), Left b) -> Right (f b)
                (_, _) -> undefined -- impossible?

instance FTraversable f => Matchable f where
    voidWith void fa = (absurd1 :: forall a. V1 a -> Void) . ftraverse ((absurd :: Void -> V1 ()) . void) $ fa

instance Matchable l => FTraversable l where
    ftraverse strat la = ???

Matchables seem interesting to me, being a part of the extended applicative family that I've been unable to find any reference to, but they really come into their own with Distributive functors from the 'distributive' library (the dual of Traversables).

Normal Distributives are isomorphic to Reader r for some r, and are famously (I presume famously, at least? It seems well known) equivalent to Representable functors, or right adjoints in Hask. Interpreted for algebraic datatypes, they are the algebraic datatypes with exactly one constructor.

These can be extended beyond the Functor-based Distribute, though!

-- all defined using cotraverse instead of distribute, for clarity
-- (which is equivalent to using distribute)

-- isomorphic to Reader r (f a) for some r and Matchable f, not sure which
-- for algebraic datatypes, those with a finite constructor count
class Functor l => MatchableDistributive l where
    cotraverseMatchable :: Matchable f => (f a -> b) -> (f (l a) -> l b)

-- isomorphic to Reader r (f a) for some r and Match f, not sure which
-- for algebraic datatypes, those with a finite non-zero constructor count
class MatchableDistributive l => MatchDistributive l where
    cotraverseMatch :: Match f => (f a -> b) -> (f (l a) -> l b)

-- isomorphic to Reader r a for some r ~ Rep l
-- for algebraic datatypes, those with exactly one constructor
class MatchDistributive l => Distributive l where
    cotraverse :: Functor f => (f a -> b) -> (f (l a) -> l b)

These mirror Traversable ~ (l (), [a]), Traversable1 ~ (l (), NonEmpty a), and the much-rarer functor-traversable ~ (l (), a).

(Of interest: for algebraic datatypes, the each Traversable family member has as many records as the Distributive equivalent has constructors, and vice versa)

(Of interest: just as Coapplicatives are trivial in Hask, so are Comatchables - I expect this can be interpreted as Coapplicatives enabling the many records of Distributive, and Comatchables enabling the many constructors of Traversable?)

Matchables also act like Applicatives for defining generic instances, except that while it's products of Applicatives that have a unique Applicative instance, it's actually sums of Matchables that have a unique instance!

Cocartesian Coapplicatives act as the Alternative equivalent - Cocartesian Coapply can be interpreted as being able to choose which side to take in an 'unzip' operation, and Cocartesian Coapplicative describes a totally uninhabited functor like V1 in generics.

class Functor f => Bias f where
    biasWith :: (c -> (a,b)) -> f c -> Either (f a) (f b)
class Bias f => Biasable f where
    devoidWith :: (a -> ()) -> (f a -> Void)

-- empty analogue
devoid :: Biasable f => f a -> Void
devoid = devoidWith (const ())

-- (<|>) analogue
biasing :: Bias f => f a -> Either (f a) (f a)
biasing = biasWith (\a -> (a,a))

In summary:

  • There are 4 types of monoid family here (monoid, comonoid, cocartesian monoid, cocartesian comonoid), of which the first and last are non-trivial in Hask. Maybe there are 6 if you include These as well as (,) and Either?

  • There are 6 members of the applicative family here (applicative, alternative, divisible, decidable, matchable, biasable), plus the trivial coapplicative and comatchable - this could presumably be filled out further to a total of 16 members! Or 36 including These as above

  • Matchable functors enable weaker versions of Distributive - where Distributive's data is always present, the data in a Match-Distributive is only sometimes present, or potentially never present in a Matchable-Distributive

(These-wise applicative = Align from the 'semialign' package, but with fewer laws? corresponding to the relationship between applicatives and zips, seen in ZipList's typeclass instances?)

Edit: A more symmetric Unfoldable instance for lists

It is possible to leave the choice to bias left or right until after-the-fact, using a similar method to the Monofoldable typeclass.

class MonoUnfoldable l e | l -> e where
    unfoldMapMono :: Split m => (m -> e) -> (m -> l)
-- this will always create an infinite list
instance MonoUnfoldable [Either a a] (Either a a) where
    unfoldMapMono strat m = strat m : unfoldMapMono strat m

pickLefts, pickRights :: [Either a a] -> [a]
pickLefts (Left a : as) = a : pickLefts as
pickLefts _ = []
pickRights (Right a : as) = a : pickRights as
pickRights _ = []

However, useful instances of Split for unfolding will be value-changing, and will have already chosen a left/right bias for these value changes. For example:

instance Num n => Split (Sum n) where
    split (Sum n)
        | n > 0     = Right $ Sum (n-1)
        | otherwise = Left  $ Sum (n-1)

unfoldMap getSum (Sum 10) = [9,8,7,6,5,4,3,2,1,0]

As such, the asymmetry is practically inherent to the process, and all that is needed is to be consistent about whether Left or Right signal an end-point for listlike (linear, finite) unfolds.

橘香 2025-02-12 23:46:37

在这个答案中(理想情况下,我应该在这个问题第一周年之前写过一段时间,但可惜),我将主要坚持协变量的单型函数,以保持范围可管理,尽管有机会考虑一些范围问题的其他方面和您的自我答复

我的另一种社论选择是在使用“共同”命名事物时行使约束。这不仅是因为当您注意到的是16个假设的单体函数类的基线时,很容易在前缀汤中迷失,​​而且还因为对于每个人来说,通常都有一个以上可见的候选人。 - “绰号。例如,请考虑应用(投入书籍单函数签名):

class Functor f => Applicative f where
    zipped :: (f a, f b) -> f (a, b)
    unit :: () -> f ()
    -- zipped = uncurry (liftA2 (,))
    -- unit = const (pure ())

一个人可能希望采用其直接的“共同应用”其直接 oplax monoidal 通过从哈斯克切换到hask op (从而绕过方法的箭头, )在将(,)保留为张量产品时:

class Functor f => Unzip f where
    unzip :: f (a, b) -> (f a, f b)
    trivialU :: f () -> ()

这些组合者对每个 fuctrotor 都有合法的实现,并使用 unzip = fmap = fmap fst&amp;&amp; fmap snd trivialu = const()。值得注意的是,这是分配文档当它注意到:

由于Haskell中缺乏非平凡的共素,我们可以限制自己需要函子而不是某些共同阶级。

除了 unzip 的微不足道外,称其为共同应用的另一个合理异议是,应用程序的彻底双重化还应替换(,)(带有的产品) (Hask Op 中的产品)。这样做会导致您的可匹配的类,我将在此处显示在此处显示,该演示文稿是从 Conor McBride :(

class Functor f => Decisive f where
    orwell :: f (Either a b) -> Either (f a) (f b)
    nogood :: f Void -> Void

值得注意的是,从协变量转换为contravariant functors时会出现类似的问题。在替换 funct> functor contravariant 应用>应用的签名中,会导致可划分,如果张量产品也以兼容的方式对双重化,我们最终以 decidiDable添加此外 和单二重奏博客文章, decidable 镜像应用程序 difsible 。)

>可划分 看起来只有符合一个值的函数可以是决定性,这只有在我们需要 orwell 作为同构时才保持。特别是,正如McBride的亮点一样,可以制作任何comoNAD(或实际上,任何具有 extract 的作用的函子)可以成为 decisive

orwellW :: Comonad w => w (Either a b) -> Either (w a) (w b)
orwellW u = case extract u of
    Left a -> Left $ either id (const a) <
gt; u
    Right b -> Right $ either (const b) id <
gt; u

nogoodW :: Comonad w => w Void -> Void
nogoodW = extract

orwellw >将提取的结果用作默认值,以编辑 案例之一。出于示范目的,以下是如何在实现过滤器的操作中部署它,该操作不仅仅是删除被拒绝的操作,而是用默认值代替它们:

import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as N

redact :: (a -> Bool) -> a -> [a] -> [a]
redact p d = N.tail . either id id . orwellW . (Right d :|) . map classify
    where
    classify a = if p a then Right a else Left a
ghci> redact (>= 0) 0 [5,-2,3,0,-1,4] 
[5,0,3,0,0,4]

在此实现中, callastify 使用用于设置的谓词基于基于的cosemigroup(换句话说,是您 split 的合法实例)。 orwellw 将此cosemigroup提升到非空的列表(假设为了保持一致性, p d true )。

(顺便说一句, redact 也想到了如何通过适当的应用程序实例实现非空列表的填充zip,如证明

决定性/可匹配的 traverable 分布,我相信它们归结为所有持有一个值的函数(左HASK内functors或您的 ftraversable )是comOnads,因此 decisive 。就这些而言,您提出的不太强大的分布最终将只是 ftraversable (在内部推动左侧伴随,而不是在外部拉出右旁边),现在我看不出如何它可能会推广到决定性


现在,让我们看一下替代。一本书单函数的演示可能是:

class Functor f => Alternative f where
    combined :: (f a, f b) -> f (Either a b)
    nil :: () -> f Void
    -- combined = uncurry (<|>) . bimap (fmap Left) (fmap Right)
    -- nil = const empty

由于我们需要在一段时间内需要它,因此值得强调的是,我们可以通过举起(&lt; |&gt;) https://stackoverflow.com/a/56200814/2751851"> Trivial 基于基于

-- either id id :: Either a a -> a
aplus :: (f a, f a) -> f a
aplus = fmap (either id id) . combined

直接的oplax oplax对应 替代恰好是某种东西熟悉:

class Functor f => Filterable f where
    partition :: f (Either a b) -> (f a, f b)
    trivialF :: f Void -> ()

这实际上等同于 mapMaybe 基于过滤 class ,详细介绍, <<<< em>每个替代的单子都可以过滤吗? 。特别是,使用 redact 以前说明的谓词Cosemigroup举起,直接导致 filter

不过,我们还没有对张量进行双重张紧。这样做会导致您的偏差(和有偏见,但是我放弃了身份以使其居住的类型):

class Functor f => Bias f where
    biased :: f (a, b) -> Either (f a) (f b)

举起Trivial (,) - 基于基于的共核心给我们:

biasing :: f a -> Either (f a) (f a)
biasing = biased . fmap (id &&& id)

bias 至少提供了形状分类器(无论 sibing 是否给出 left 取决于其参数的函数形状)以及对组件之间的相关选择。我之所以说“至少”,是因为将身份丢弃偏见仅使用关联法,并说法律不排除 f shape的更改或重新排列,为只要它们是势力,并保留/分类。

如果偏见如此对法律的看法是一个问题,则有一种合理的方法可以将其拧紧。为了提出它,让我们回到应用替代暂时。虽然在单型函子公式中替代在原则上独立于应用程序 ,但有时还有一些其他可能的法律被调用以连接这两个类并更好地描述的含义替代(有关此的评论,请参见 “替代”类型类及其与其他类型类的关系 )。这些定律之一是(&lt;*&gt;) (&lt; |&gt;)的正确分发性:

(u <|> v) <*> w = (u <*> w) <|> (v <*> w)

我们可以将其转换为我们在此处使用的词汇...

zipped (aplus (u, v), w) = aplus (zipped (u, w), zipped (v w)

...并使它变得无点,为了更轻松的操作:

zipped . first aplus = aplus . bimap zipped zipped . distribR

distribR :: (((a, b), c) -> ((a, c), (b, c))
distribR ((a, b), c) = ((a, c), (b, c))

现在,如果 bias 是双重的, alt 替代 sans sans Identity)和>决定性应用是双重的。

first biasing . orwell = codistribR . bimap orwell orwell . biasing

codistribR :: Either (Either a c) (Either b c) -> Either (Either a b) c
codistribR = \case
    Left (Left a) -> Left (Left a)
    Left (Right c) -> Right c
    Right (Left b) -> Left (Right b)
    Right (Right c) -> Right c

采用该法律(和/或类似的左发行法)将禁止偏见(并且,按扩展, )从更改形状。这是因为,按照决定性的身份定律, orwell 无法更改形状,这特别是:

first biasing (orwell (Right <
gt; u)) = Right u

应用分配法,然后才会导致

codistribR (bimap orwell orwell (biasing (Right <
gt; u)) = Right u

偏见使形状未触及。因此,分发性定律可以确保有偏见是一个形状分类器,并在 fmap fst fmap snd 之间进行选择。所有这些都非常整洁地突出了替代/ alt bias 之间的对应关系,还可以在应用程序和决定性

In this answer (which I ideally should have written some time before the first anniversary of the question, but alas), I will mostly stick to covariant monoidal functors in order to keep the scope manageable, though there will be opportunity to consider some of the other aspects of the question and of your self-answer.

Another editorial choice of mine will be exercising restraint in using "co-" to name things. That's not just because it's easy to get lost in a soup of prefixes when, as you note, there is a baseline of sixteen hypothetical monoidal functor classes, but also because for each of them there generally is more than one plausible candidate for the "co-" moniker. For instance, consider Applicative (cast into a by-the-book monoidal functor signatures):

class Functor f => Applicative f where
    zipped :: (f a, f b) -> f (a, b)
    unit :: () -> f ()
    -- zipped = uncurry (liftA2 (,))
    -- unit = const (pure ())

One might want to adopt as "coapplicative" its straightforward oplax monoidal counterpart, obtained by switching from Hask to Haskop (and thus turning around the arrows of the methods) while keeping (,) as the tensor product:

class Functor f => Unzip f where
    unzip :: f (a, b) -> (f a, f b)
    trivialU :: f () -> ()

These combinators have a lawful implementation for every Functor, with unzip = fmap fst &&& fmap snd and trivialU = const (). Notably, this is the coapplicative alluded to by the distributive documentation when it notes that:

Due to the lack of non-trivial comonoids in Haskell, we can restrict ourselves to requiring a Functor rather than some Coapplicative class.

Besides the triviality of Unzip, another plausible objection to calling it coapplicative is that a thorough dualisation of applicative should also replace (,) (the product in Hask) with Either (the product in Haskop). Doing so leads to your Matchable class, which I'll display here in a presentation borrowed from a post by Conor McBride :

class Functor f => Decisive f where
    orwell :: f (Either a b) -> Either (f a) (f b)
    nogood :: f Void -> Void

(It is worth noting that similar questions arise when switching from covariant to contravariant functors. While replacing Functor with Contravariant in the signatures of Applicative leads to Divisible, if the tensor products are also dualised in a compatible way we end up with Decidable instead. Furthermore, as argued in my Divisible and the monoidal quartet blog post, Decidable mirrors Applicative more closely than Divisible.)

While at first it might look like only functors holding exactly one value can be Decisive, that only holds if we require orwell to be an isomorphism. In particular, as McBride highlights, any comonad (or really, any functor with something to play the role of extract) can be made Decisive:

orwellW :: Comonad w => w (Either a b) -> Either (w a) (w b)
orwellW u = case extract u of
    Left a -> Left $ either id (const a) <
gt; u
    Right b -> Right $ either (const b) id <
gt; u

nogoodW :: Comonad w => w Void -> Void
nogoodW = extract

orwellW uses the result of extract as a default value, in order to redact one of the Either cases. For demonstrative purposes, here is how it might be deployed in implementing a filter-resembling operation which, instead of merely removing rejected, replaces them with a default:

import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as N

redact :: (a -> Bool) -> a -> [a] -> [a]
redact p d = N.tail . either id id . orwellW . (Right d :|) . map classify
    where
    classify a = if p a then Right a else Left a
ghci> redact (>= 0) 0 [5,-2,3,0,-1,4] 
[5,0,3,0,0,4]

In this implementation, classify uses the predicate to set up an Either-based cosemigroup (in other words, a lawful instance of your Split). orwellW lifts this cosemigroup to non-empty lists (assuming that, for the sake of consistency, p d is True).

(redact, by the way, also brings to mind how a padding zip can be implemented through a suitable Applicative instance for non-empty lists, as demonstrated in this answer, incidentally also written by McBride, though at the moment I'm not sure about how deep the relationship really is.)

On the connections between Decisive/Matchable, Traversable and Distributive, I believe they boil down to all functors holding exactly one value (the left adjoint Hask endofunctors, or your FTraversable) being comonads, and thus Decisive. As far as these are concerned, the less powerful distributive you propose would ultimately be just FTraversable (pushing a left adjoint inside, rather than pulling a right adjoint outside), and right now I don't see how it might generalise to Decisive.


Now let's look at Alternative. A by-the-book monoidal functor presentation might be:

class Functor f => Alternative f where
    combined :: (f a, f b) -> f (Either a b)
    nil :: () -> f Void
    -- combined = uncurry (<|>) . bimap (fmap Left) (fmap Right)
    -- nil = const empty

Since we'll need it in a little while, it's worth emphasising we can recover (<|>) by lifting the trivial Either-based monoid:

-- either id id :: Either a a -> a
aplus :: (f a, f a) -> f a
aplus = fmap (either id id) . combined

The straightforward oplax counterpart to Alternative happens to be something familiar:

class Functor f => Filterable f where
    partition :: f (Either a b) -> (f a, f b)
    trivialF :: f Void -> ()

This is actually equivalent to the familiar, mapMaybe-based Filterable class, as covered in detail by Is every Alternative Monad Filterable?. In particular, using the predicate cosemigroup lifting illustrated before with redact leads directly to filter.

We haven't dualised the tensors yet, though. Doing so leads to your Bias (and Biasable, but I'm giving up on the identity in order to have inhabited types):

class Functor f => Bias f where
    biased :: f (a, b) -> Either (f a) (f b)

Lifting the trivial (,)-based comonoid gives us:

biasing :: f a -> Either (f a) (f a)
biasing = biased . fmap (id &&& id)

Bias offers, at least, a classifier of shapes (whether biasing gives out Left or Right depends on the functorial shape of its argument), plus an associated choice between pair components. I say "at least" because dropping the identity leaves Bias with just an associativity law, and said law doesn't rule out changes or rearrangements of the f-shape, as long as they are idempotent and preserve the Left/Right classification.

If Bias being so light on laws is deemed a problem, there is one plausible way of tightening it up somewhat. In order to present it, let's go back to Applicative and Alternative for a moment. While in the monoidal functor formulation Alternative is in principle independent from Applicative, there are a few other possible laws that are sometimes invoked to connect the two classes and better delineate the meaning of Alternative (for commentary on that, see Antal Spector-Zabusky's answer to Confused by the meaning of the 'Alternative' type class and its relationship to other type classes). One of these laws is right distributivity of (<*>) over (<|>):

(u <|> v) <*> w = (u <*> w) <|> (v <*> w)

We can translate that to the vocabulary we're using here...

zipped (aplus (u, v), w) = aplus (zipped (u, w), zipped (v w)

... and make it pointfree, for the sake of easier manipulation:

zipped . first aplus = aplus . bimap zipped zipped . distribR

distribR :: (((a, b), c) -> ((a, c), (b, c))
distribR ((a, b), c) = ((a, c), (b, c))

Now, if Bias is dual to Alt (Alternative sans identity) and Decisive is dual to Applicative, we should be able to dualise the distributivity law for a functor which is both Decisive and Bias:

first biasing . orwell = codistribR . bimap orwell orwell . biasing

codistribR :: Either (Either a c) (Either b c) -> Either (Either a b) c
codistribR = \case
    Left (Left a) -> Left (Left a)
    Left (Right c) -> Right c
    Right (Left b) -> Left (Right b)
    Right (Right c) -> Right c

Adopting this law (and/or the analogous left distributivity law) would forbid biasing (and, by extension, biased) from changing the shape. That's because, by the identity laws of Decisive, orwell can't change shapes, which means, in particular:

first biasing (orwell (Right <
gt; u)) = Right u

Applying the distributive law then leads to:

codistribR (bimap orwell orwell (biasing (Right <
gt; u)) = Right u

Which is only possible if biasing leaves the shape untouched. The distributivity law, therefore, can ensure biased is a shape classifier with bundled with a choice between fmap fst and fmap snd. That it all works out so neatly highlights the correspondence not just between Alternative/Alt and Bias, but also between Applicative and Decisive.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文