Lax monoidal仿函数具有不同的幺半群结构

时间:2014-04-26 20:38:39

标签: haskell functor applicative category-theory

在Haskellers中,应用函子是众所周知的,并且因其在有效环境中应用函数的能力而备受青睐。

在类别理论术语中,可以显示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,然后写(<*>)你将函数和参数压缩成一个元组然后在其上映射一个合适的应用程序函数。

此外,这种对应关系将Applicative定律转化为关于unit(**)的自然幺正定律,所以实际上一个应用函子恰恰是理论家称之为松散的类别。 monoidal仿函数(松弛因为(**)仅仅是一个自然变换,而不是同构)。

好的,很好,很棒。这是众所周知的。但这只是一系列松散的幺正仿函数 - 那些尊重产品的幺半群结构。松散的monoidal仿函数涉及两种幺半群结构的选择,在源和目的地:如果你把产品变成总和,你就得到了:

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 -> f Void具有唯一的确定性,似乎将总和变成其他幺半群结构变得不那么有趣,所以你真的有更多的半群继续进行。但仍然:

  • 上面研究过的其他松散的幺正算子还是有用的吗?
  • 是否有像Applicative一样的完整替代演示文稿?

3 个答案:

答案 0 :(得分:15)

在你甚至可以谈论monoidal仿函数之前,你需要确保你在monoidal category。碰巧 Hask 是以下列方式的幺半群类别:

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

正如您所观察到的,当您为()交换Void(,)交换Either时,它也是幺半群类别。 然而,monoidal不会让你走得太远 - 使 Hask 如此强大的原因是cartesian closed。这给了我们currying和相关的技术,没有它们,应用程序将毫无用处。

如果一个幺半群类别的身份是terminal object,即一个的类型,其中恰好存在一个(当然,我们在这里忽略))箭头,它可以是笛卡尔闭合的。任何类型A -> ()都有一个函数A,即const ()。但是没有函数A -> Void。相反,Void初始对象:正好存在一个来自的箭头,即absurd :: Void -> 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)

答案 1 :(得分:15)

&#34;整洁的替代演示&#34; for Applicative基于以下两个等价

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

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

获得这个&#34;整洁的替代演示文稿&#34; for ApplicativezipWith的技巧相同 - 将接口中的显式类型和构造函数替换为可以传递类型或构造函数的东西,以恢复原始接口的内容。

unit :: f ()

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

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

同样(尽管不是那么简单)将类型(a,b)和构造函数(,) :: a -> b -> (a,b)替换为liftA2以恢复**

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

Applicative然后通过将函数应用程序<*>提升到仿函数中来获取漂亮的($) :: (a -> b) -> a -> b运算符。

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

找到一个整洁的替代演示文稿&#34;对于PtS,我们需要找到

  • 我们可以将Void类型替换为unit
  • 我们可以将类型Either a b和构造函数Left :: a -> Either a bRight :: b -> 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)

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

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中表达这样的约束。

答案 2 :(得分:8)

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

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

唯一的问题当然是AlternativeApplicative的扩展。然而,也许人们可以想象它是单独呈现的,然后与Applicative的组合让人联想到具有非交换环状结构的仿函数,其中两个幺半群结构作为环的操作? ApplicativeAlternative IIRC之间也存在分配法。