import Data.Monoid
import Control.Applicative
让我们追溯一个Monoid和Alternative与Maybe
函子和ZipList
函子相互作用的示例,但让我们从头开始,一方面让所有的定义都浮现在脑海,另一方面让我们始终避免将标签切换为黑客行为,但是主要是为了让我可以通过ghci来纠正我的错字!
(<>) :: Monoid a => a -> a -> a
(<>) = mappend
这是Maybe克隆:
data Perhaps a = Yes a | No deriving (Eq, Show)
instance Functor Perhaps where
fmap f (Yes a) = Yes (f a)
fmap f No = No
instance Applicative Perhaps where
pure a = Yes a
No <*> _ = No
_ <*> No = No
Yes f <*> Yes x = Yes (f x)
现在是ZipList:
data Zip a = Zip [a] deriving (Eq,Show)
instance Functor Zip where
fmap f (Zip xs) = Zip (map f xs)
instance Applicative Zip where
Zip fs <*> Zip xs = Zip (zipWith id fs xs)
pure a = Zip (repeat a)
结构1:组合元素:Monoid
也许克隆
首先让我们看一下Perhaps String
。有两种组合方式。首先串联
(<++>) :: Perhaps String -> Perhaps String -> Perhaps String
Yes xs <++> Yes ys = Yes (xs ++ ys)
Yes xs <++> No = Yes xs
No <++> Yes ys = Yes ys
No <++> No = No
通过将No
好像当作,串联实际上在String级别上起作用,而不是在About级别上起作用Yes []
。等于liftA2 (++)
。这是明智且有用的,但也许我们可以概括一下++
,从使用到使用任何组合方式-然后是任何Monoid!
(<++>) :: Monoid a => Perhaps a -> Perhaps a -> Perhaps a
Yes xs <++> Yes ys = Yes (xs `mappend` ys)
Yes xs <++> No = Yes xs
No <++> Yes ys = Yes ys
No <++> No = No
这种monoid结构用于Perhaps
尝试在a
级别上尽可能多地工作。注意Monoid a
约束,告诉我们我们正在使用该a
级别的结构。这不是替代结构,而是派生(提升)的Monoid结构。
instance Monoid a => Monoid (Perhaps a) where
mappend = (<++>)
mempty = No
在这里,我使用了数据的结构a来增加整个结构。如果要合并Set
s,则可以添加Ord a
上下文。
ZipList克隆
那么我们应该如何将元素与zipList结合在一起?如果我们将它们组合在一起,应该压缩到什么位置?
Zip ["HELLO","MUM","HOW","ARE","YOU?"]
<> Zip ["this", "is", "fun"]
= Zip ["HELLO" ? "this", "MUM" ? "is", "HOW" ? "fun"]
mempty = ["","","","",..]
但是我们应该用什么?
。我说这是唯一明智的选择++
。实际上,对于列表,(<>) = (++)
Zip [Just 1, Nothing, Just 3, Just 4]
<> Zip [Just 40, Just 70, Nothing]
= Zip [Just 1 ? Just 40, Nothing ? Just 70, Just 3 ? Nothing]
mempty = [Nothing, Nothing, Nothing, .....]
但是我们可以用来?
表示要组合元素,因此我们应该再次使用Monoid中的元素组合运算符:<>
。
instance Monoid a => Monoid (Zip a) where
Zip as `mappend` Zip bs = Zip (zipWith (<>) as bs)
mempty = Zip (repeat mempty)
这是使用zip组合元素的唯一明智的方法-因此,这是唯一明智的monoid实例。
有趣的是,这对于上面的Maybe示例不起作用,因为Haskell不知道如何组合Int
s-应该使用+
还是使用*
?要获取数字数据上的Monoid实例,可以将其包装Sum
或Product
告知要使用哪个Monoid 。
Zip [Just (Sum 1), Nothing, Just (Sum 3), Just (Sum 4)] <>
Zip [Just (Sum 40), Just (Sum 70), Nothing]
= Zip [Just (Sum 41),Just (Sum 70), Just (Sum 3)]
Zip [Product 5,Product 10,Product 15]
<> Zip [Product 3, Product 4]
= Zip [Product 15,Product 40]
关键点
注意Monoid中的类型具有种类这一事实*
正是允许我们将Monoid a
上下文放在此处的事实-我们也可以添加Eq a
或Ord a
。在Monoid中,原始元素很重要。Monoid实例旨在允许您操纵和组合结构内部的数据。
结构2:更高层次的选择:替代
选择运算符相似,但也不同。
也许克隆
(<||>) :: Perhaps String -> Perhaps String -> Perhaps String
Yes xs <||> Yes ys = Yes xs
Yes xs <||> No = Yes xs
No <||> Yes ys = Yes ys
No <||> No = No
这里没有连接-我们根本没有使用++
-这种组合仅在Perhaps
级别上有效,因此让我们将类型签名更改为
(<||>) :: Perhaps a -> Perhaps a -> Perhaps a
Yes xs <||> Yes ys = Yes xs
Yes xs <||> No = Yes xs
No <||> Yes ys = Yes ys
No <||> No = No
请注意,没有任何约束-我们没有使用a
关卡中的结构,而只是使用关卡中的结构Perhaps
。这是另一种结构。
instance Alternative Perhaps where
(<|>) = (<||>)
empty = No
ZipList克隆
我们应该如何在两个ziplist之间进行选择?
Zip [1,3,4] <|> Zip [10,20,30,40] = ????
<|>
在元素上使用将是非常诱人的,但是我们不能,因为元素的类型对我们不可用。让我们从开始empty
。它不能使用元素,因为在定义Alternative时我们不知道元素的类型,因此必须为Zip []
。我们需要的是一个左(最好右)的身份<|>
,所以
Zip [] <|> Zip ys = Zip ys
Zip xs <|> Zip [] = Zip xs
有两个明智的选择Zip [1,3,4] <|> Zip [10,20,30,40]
:
Zip [1,3,4]
因为它是第一位-与Maybe一致
Zip [10,20,30,40]
因为它最长-与Zip []
被丢弃一致
这很容易决定:由于pure x = Zip (repeat x)
,两个列表可能是无限的,因此比较它们的长度可能永远不会终止,因此必须选择第一个。因此,唯一明智的替代实例是:
instance Alternative Zip where
empty = Zip []
Zip [] <|> x = x
Zip xs <|> _ = Zip xs
这是我们可以定义的唯一明智的选择。请注意,它与Monoid实例有何不同,因为我们无法弄乱元素,甚至无法查看它们。
关键点
请注意,由于Alternative
采用的是构造函数,* -> *
因此无法添加Ord a
orEq a
或Monoid a
context。不允许替代方案使用有关结构内部数据的任何信息。无论您要多少,您都不能对数据做任何事情,除非可能会将其丢弃。
关键点:替代方案和Monoid有什么区别?
并不是很多-它们都是monoid,但总结一下最后两节:
Monoid *
实例使合并内部数据成为可能。 Alternative (* -> *)
实例使之成为不可能。Monoid提供灵活性,Alternative提供保证。种类*
和(* -> *)
是造成这种差异的主要驱动力。同时拥有它们可以让您使用两种操作。
这是对的,我们的两种口味都合适。Monoid实例Perhaps String
表示将所有字符组合在一起,Alternative实例表示在字符串之间进行选择。
Mayid的Monoid实例没有任何问题-它正在完成其工作,合并数据。
Maybe的Alternative实例没有任何问题-它正在完成工作,在事物之间进行选择。
Zip的Monoid实例结合了其元素。Zip的Alternative实例被迫选择一个列表-第一个非空列表。
能够同时做到这两个很好。
适用上下文有什么用?
选择和应用之间存在一些相互作用。请参阅此处的问题或回答中间提到的Antal SZ的法律。
从实用的角度来看,它是有用的,因为“替代”是供某些应用函子选择的东西。该功能已用于Applicatives,因此发明了通用接口类。应用函子非常适合表示产生值的计算(IO,解析器,输入UI元素等),其中一些必须处理故障-需要替代方法。
为什么会有替代品empty
?
为什么Alternative需要一个空的方法/成员?我可能是错的,但似乎根本没有使用它……至少在我能找到的代码中。而且这似乎与课程的主题不符-如果我有两件事,并且需要选择其中一件事,我需要做什么?
这就像在问为什么加法需要0-如果您要添加东西,那么什么都不添加什么有什么意义呢?答案是0是所有要素都围绕着旋转的关键枢纽数,就像1对乘法[]
至关重要,对列表y=e^x
至关重要(对微积分至关重要)一样。实际上,您可以使用以下禁止操作元素来开始构建:
sum = foldr (+) 0
concat = foldr (++) []
msum = foldr (`mappend`) mempty
whichEverWorksFirst = foldr (<|>) empty
我们不能用Monad + Alternative替代MonadPlus吗?
MonadPlus类型类有什么意义?我不能仅使用Monad和Alternative来释放其所有优点吗?为什么不干掉它呢?(我确定我错了,但是我没有任何反例)
您没看错,没有任何反例!
您的有趣问题是Antal SZ,PetrPudlák和我研究了MonadPlus和Applicative之间的真正关系。答案就
在这里
和这里,
是什么MonadPlus
(从左分布的意义-请单击链接以获取详细信息)也是Alternative
,但并非相反。
这意味着,如果您创建Monad和MonadPlus的实例,则无论如何都满足适用性和替代性的条件。这意味着,如果您遵循MonadPlus(左距)的规则,则您可能还使Monad成为了Applicative并使用了Alternative。
但是,如果删除MonadPlus类,则会删除要记录的规则的合理位置,并且您将无法指定某个替代项而不是MonadPlus(从技术上来说,我们应该为Maybe做)。这些是理论上的原因。实际原因是它会破坏现有代码。(这也是为什么Applicative和Functor都不是Monad的超类的原因。)
替代和Monoid不一样吗?替代和Monoid不完全不同吗?
'pedia说:“ Alternate类型类适用于也具有类半体结构的Applicative函数。” 我不明白这一点-“替代”是否意味着与Monoid完全不同?也就是说,我理解Alternative类型类的要点是在两件事之间进行选择,而我将Monoids理解为要结合事物。
Monoid和Alternative是以明智的方式从两个对象中获取一个对象的两种方法。数学并不关心您是要选择,组合,混合还是分解数据,这就是为什么Alternative被称为Applicative Monoid的原因。您现在似乎已经有了这个概念,但是现在您说
对于同时具有Alternative和Monoid实例的类型,这些实例应相同
我不同意这一点,并且我认为我的Maybe和ZipList示例经过了仔细的解释,以说明它们为何与众不同。如果有的话,我认为这是罕见的。我只能想到一个适当的示例,即纯列表。这是因为列表是等式的基本示例++
,但是列表在某些情况下还用作元素的不确定选择,因此<|>
也应该使用++
。