【问题标题】:Lax monoidal functors with a different monoidal structure具有不同幺半群结构的松散幺半群函子
【发布时间】:2014-06-12 12:48:28
【问题描述】:

应用函子在 Haskeller 中广为人知并深受喜爱,因为它们能够在有效的上下文中应用函数。

在范畴论方面,可以证明Applicative的方法:

pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b

相当于拥有Functor f的操作:

unit :: f ()
(**) :: (f a, f b) -> f (a,b)

想法是写pure你只需用给定的值替换unit中的(),写(&lt;*&gt;)你把函数和参数压缩成一个元组,然后映射一个合适的应用程序函数在它上面。

此外,这种对应将Applicative 定律转化为关于unit(**) 的自然幺半群定律,所以实际上应用函子正是范畴论者所说的松散单曲面函子(松散是因为@ 987654333@ 只是一个自然变换而不是同构)。

好的,很好,很好。这是众所周知的。但这只是松散的单曲面函子的一个家族——那些尊重乘积的单曲面结构的函子。松散的幺半群函子在源和目标中涉及两种幺半群结构选择:如果将乘积转换为总和,您会得到以下结果:

class PtS f where
  unit :: f Void
  (**) :: f a -> f b -> f (Either a b)

-- some example instances
instance PtS Maybe where
  unit = Nothing
  Nothing ** Nothing = Nothing
  Just a ** Nothing = Just (Left a)
  Nothing ** Just b = Just (Right b)
  Just a ** Just b = Just (Left a) -- ick, but it does satisfy the laws

instance PtS [] where
  unit = []
  xs ** ys = map Left xs ++ map Right ys

unit :: Void -&gt; f Void 是唯一确定的,似乎将 sum 转换为其他幺半群结构变得不那么有趣了,所以你真的有更多的半群在进行。但还是:

  • 其他像上述那样的松弛幺半函数是否研究过或有用?
  • 有没有像Applicative 这样的简洁替代演示?

【问题讨论】:

  • 当你在PtS.unit的类型中说Void时,你的意思是不是空的,因为它应该是Either的一个单位?
  • 没关系,您可能打算将Void 表示为空类型。我很困惑,因为类 C 语言中的名称 void 对应于单位类型,您将其写为 ()
  • 是的,很抱歉造成混乱,但有合理的先例:hackage.haskell.org/package/void
  • Ack,可以说是 C 对 void 的使用是错误的,而不是你的 ;)

标签: haskell functor applicative category-theory alternative-functor


【解决方案1】:

Applicative 的“简洁的替代表示”基于以下两个等效项

pure a = fmap (const a) unit
unit = pure ()

ff <*> fa = fmap (\(f,a) -> f a) $ ff ** fa
fa ** fb = pure (,) <*> fa <*> fb

Applicative 获得这种“简洁的替代表示”的技巧与zipWith 的技巧相同 - 将接口中的显式类型和构造函数替换为可以传递类型或构造函数以恢复什么原来的界面是。

unit :: f ()

替换为pure,我们可以将类型()和构造函数() :: ()替换成恢复unit

pure :: a  -> f a
pure    () :: f ()

同样(虽然不是那么简单)将类型 (a,b) 和构造函数 (,) :: a -&gt; b -&gt; (a,b) 替换为 liftA2 以恢复 **

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2    (,)           :: f a -> f b -> f (a,b)

Applicative 然后通过将函数应用程序($) :: (a -&gt; b) -&gt; a -&gt; b 提升到函子中来获得漂亮的&lt;*&gt; 运算符。

(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 ($)

要为PtS 找到一个“简洁的替代演示文稿”,我们需要找到

  • 我们可以将Void 类型替换为恢复unit
  • 我们可以将Either a b 类型和构造函数Left :: a -&gt; Either a bRight :: b -&gt; Either a b 替换为恢复**

(如果您注意到我们已经有了构造函数 LeftRight 可以传递给您的东西,那么您可能会弄清楚我们可以用什么替换 ** 而无需遵循我使用的步骤;我没有注意到直到我解决它之后)

单位

这立即为我们提供了unit 的替代方案:

empty :: f a
empty = fmap absurd unit

unit :: f Void
unit = empty

运营商

我们希望找到(**) 的替代方案。除了像Either 这样的总和之外,还有一种替代方法,它允许将它们编写为乘积的函数。它在不存在总和的面向对象编程语言中显示为访问者模式。

data Either a b = Left a | Right b

{-# LANGUAGE RankNTypes #-}
type Sum a b = forall c. (a -> c) -> (b -> c) -> c

如果你改变either的参数的顺序并部分应用它们,你会得到什么。

either :: (a -> c) -> (b -> c) -> Either a b -> c

toSum :: Either a b -> Sum a b
toSum e = \forA forB -> either forA forB e

toEither :: Sum a b -> Either a b
toEither s = s Left Right

我们可以看到Either a b ≅ Sum a b。这允许我们重写(**)的类型

(**) :: f a -> f b -> f (Either a b)
(**) :: f a -> f b -> f (Sum a b)
(**) :: f a -> f b -> f ((a -> c) -> (b -> c) -> c)

现在很清楚** 做了什么。它将fmaping 延迟到它的两个参数上,并结合这两个映射的结果。如果我们引入一个新的运算符&lt;||&gt; :: f c -&gt; f c -&gt; f c,它只是假设fmaping 已经完成,那么我们可以看到

fmap (\f -> f forA forB) (fa ** fb) = fmap forA fa <||> fmap forB fb

或者返回Either

fa ** fb = fmap Left fa <||> fmap Right fb
fa1 <||> fa2 = fmap (either id id) $ fa1 ** fa2

所以我们可以用下面的类来表达PtS可以表达的所有东西,并且可以实现PtS的所有东西都可以实现下面的类:

class Functor f => AlmostAlternative f where
    empty  :: f a
    (<||>) :: f a -> f a -> f a

这几乎肯定与Alternative 类相同,只是我们不要求FunctorApplicative

结论

它只是一个Functor,即所有类型的Monoid。它相当于以下内容:

class (Functor f, forall a. Monoid (f a)) => MonoidalFunctor f

forall a. Monoid (f a) 约束是伪代码;我不知道如何在 Haskell 中表达这样的约束。

【讨论】:

  • 完美!现在很生气我自己没有发现这个:P(而且,诅咒你,你给了我一个重要的选择来接受哪个答案)
  • (小点:您将总和描述为类似于“乘积的函数”,您的意思是“函数的乘积”吗?)
  • 不,我指的是产品的功能。 sum 是一个接受两个函数乘积的函数。您可以通过传入两个函数(两个函数的乘积)从总和中获取数据——一个用于第一个选项的操作,一个用于第二个选项的操作。如果 sum 是第一个选项,它会从 product 中获取第一个函数,将其应用于 sum 包含的内容,然后返回结果。如果 sum 是第二个选项,它会从乘积中获取第二个函数,将其应用于 sum 包含的内容,然后返回结果。 sum 是一个函数,它采用函数的乘积。
  • 哦,我明白你的意思了,是的。
  • 现在可以在 Haskell 中用 -XQuantifiedConstraints 表示 forall a. Monoid (f a)
【解决方案2】:

在您谈论幺半群函子之前,您需要确保您在monoidal category 中。 Hask 恰好是一个幺半群的类别,如下所示:

  • () 身份
  • (,) 作为双函子
  • 识别同构类型,即(a,()) ≅ ((),a) ≅ a(a,(b,c)) ≅ ((a,b),c)

正如您所观察到的,当您将() 换成Void(,) 换成Either 时,它也是一个幺半群类别。
然而,monoidal 并不能让你走得太远——Hask 如此强大的原因在于它是cartesian closed。这为我们提供了 currying 和相关技术,没有这些 applicative 几乎毫无用处。

一个幺半群类别可以是笛卡尔封闭的,如果它的身份是terminal object,即一个类型onto,恰好存在一个(当然,我们在这里忽略⟂)箭头。对于任何类型A,都有一个函数A -&gt; (),即const ()。但是没有功能A -&gt; Void。相反,Void初始对象:恰好存在一个箭头来自它,即absurd :: Void -&gt; a 方法。这样的幺半群类别不能是笛卡尔封闭的。
现在,当然,您可以通过转动箭头方向轻松地在初始和终结之间切换。这总是将您置于双重结构中,因此我们得到cocartesian closed category。但这意味着您还需要翻转单曲面函子中的箭头。那些被称为decisive functors然后(并概括comonads)。凭借 Conor 令人惊叹的命名方案,

class (Functor f) => Decisive f where
  nogood :: f Void -> Void
  orwell :: f (Either s t) -> Either (f s) (f t)

【讨论】:

  • 我知道这并不能真正回答您的问题。 Monoidal functor WRT coproducts 在某种程度上可能仍然很有趣,但我想像 unit 这样的麻烦就像你说的那样微不足道,这在很大程度上阻碍了这一点。
  • 当我读完第一句话时,我就知道你一定是这篇文章的作者了。
【解决方案3】:

我在范畴论方面的背景非常有限,但是 FWIW,你的 PtS 课让我想起了 Alternative class,它看起来基本上是这样的:

class Applicative f => Alternative f where
  empty :: f a
  (<|>) :: f a -> f a -> f a

当然唯一的问题是AlternativeApplicative 的扩展。但是,也许可以想象它被单独呈现,与Applicative 的组合很容易让人联想到具有非交换类环状结构的函子,两个幺半群结构作为环的操作? ApplicativeAlternative IIRC 之间也存在分配规律。

【讨论】:

  • +1 用于直接解决问题。在手工解决问题后,我得出了相同的答案。
猜你喜欢
  • 2018-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多