在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
一样的完整替代演示文稿?答案 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 Applicative
与zipWith
的技巧相同 - 将接口中的显式类型和构造函数替换为可以传递类型或构造函数的东西,以恢复原始接口的内容。
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 b
和Right :: b -> Either a b
替换为**
(如果您注意到我们已经有一些构造函数Left
和Right
可以传递给您,那么您可以在不遵循我使用的步骤的情况下找出我们可以替换**
的内容;在我解决之前,我没有注意到这一点)
这使得我们可以替代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
类相同,但我们并不要求Functor
为Applicative
。
对于所有类型而言,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
唯一的问题当然是Alternative
是Applicative
的扩展。然而,也许人们可以想象它是单独呈现的,然后与Applicative
的组合让人联想到具有非交换环状结构的仿函数,其中两个幺半群结构作为环的操作? Applicative
和Alternative
IIRC之间也存在分配法。