感到困惑的是'Alternative'类型类的含义及其与其他类型类的关系

Mat*_*ick 59 haskell typeclass

我一直在浏览Typeclassopedia来学习类型类.我不理解Alternative(MonadPlus就此而言).

我遇到的问题:

  • 'pedia说"替代类型类适用于具有幺半群结构的Applicative仿函数." 我不明白 - 不是替代意味着与Monoid完全不同的东西吗?也就是说,我理解了替代类型类在两件事之间的选择,而我理解Monoids是关于组合事物.

  • 为什么Alternative需要一个empty方法/成员?我可能错了,但似乎根本没有使用...至少在我能找到的代码中.它似乎不符合课堂主题 - 如果我有两件事,需要选择一件,我需要什么'空'?

  • 为什么Alternative类需要一个Applicative约束,为什么它需要一种* -> *?为什么不<|> :: a -> a -> a呢?所有的实例仍然可以用同样的方式实现......我想(不确定).Monoid没有提供什么价值?

  • 什么是点MonadPlus式类?我不能解开所有善良的通过只是使用的东西既是MonadAlternative?为什么不放弃呢?(我确定我错了,但我没有任何反例)

希望所有这些问题都是连贯的......!


赏金更新:@Antal的答案是一个很好的开始,但Q3仍然是开放的:替代品提供的Monoid不是什么?我发现这个答案不能令人满意,因为它缺乏具体的例子,并且特别讨论了Alternative的高知名度如何将它与Monoid区分开来.

如果要将应用效果与Monoid的行为结合起来,为什么不只是:

liftA2 mappend
Run Code Online (Sandbox Code Playgroud)

这对我来说更加令人困惑,因为许多Monoid实例与Alternative实例完全相同.

这就是为什么我要寻找具体的例子,说明为什么备选是必要的,以及它与Monoid的不同之处 - 或者意味着不同的东西.

Ant*_*sky 77

首先,让我为每个问题提供简短的答案.然后我会将每个扩展为更详细的答案,但这些简短的答案将有助于导航这些.

  1. 没有,Alternative并且Monoid不意味着不同的事情; Alternative对于具有两者的结构类型Applicative和的Monoid.对于相同的更广泛的概念,"拣选"和"结合"是两种不同的直觉.

  2. Alternative包含empty以及<|>因为设计师认为这将是有用的,因为这会产生一个幺半群.在采摘方面,empty对应于做出不可能的选择.

  3. 我们需要两者Alternative,Monoid因为前者服从(或应该)比后者更多的法律; 这些定律涉及类型构造函数的幺半群和应用结构.另外,Alternative可以不依赖于内在类型Monoid.

  4. MonadPlus比略强Alternative,因为它必须遵守更多的法律; 除了应用结构之外,这些定律还将幺半群结构与单子结构联系起来.如果你有两者的实例,它们应该重合.


并不Alternative意味着完全不同的东西Monoid

并不是的!您混淆的部分原因是Haskell Monoid类使用了一些非常糟糕(通常不够通用)的名称.这就是数学家如何定义一个monoid(非常明确):

定义.半群是一组中号配备有杰出的元件M和二元运算符·:M × MM,由并置表示,使得以下两个条件成立:

  1. 是身份:对于所有mM,m = m = m.
  2. ·是关联的:对于所有m?,m?,m??M,(mm?)m?= m?(mm?).

而已.在哈斯克尔,?拼写mempty和·拼写mappend(或者,这些天,<>),以及该组中号的类型是Minstance Monoid M where ....

看看这个定义,我们看到它没有说"组合"(或关于"挑选").它说的是关于什么的?,但就是这样.现在,这是千真万确的事情结合这种结构运行良好:对应没有东西,而m?说如果我据为己有?和m?的东西在一起,我可以得到一个包含所有东西的新东西.但这是另一种直觉:根本没有选择,而m?对应m之间的选择?和m?.这是"挑选"的直觉.请注意,两者都遵守幺半群法则:

  1. 什么都没有,别无选择都是身份.
    • 如果我没有任何东西并且与某些东西一起闪现,我最终会再次使用相同的东西.
    • 如果我可以选择(完全没有选择)和其他选择,我必须选择其他(可能的)选择.
  2. 将各种颜色集合在一起并做出选择都是相关的.
    • 如果我有三个集合,那么如果我将前两个集合在一起然后是第三个集合,或者最后两个集合在一起然后是第一个集合并不重要; 无论哪种方式,我最终得到相同的总glommed集合.
    • If I have a choice between three things, it doesn’t matter if I (a) first choose between first-or-second and third and then, if I need to, between first and second, or (b) first choose between first and second-or-third and then, if I need to, between second and third. Either way, I can pick what I want.

(Note: I’m playing fast and loose here; that’s why it’s intuition. For instance, it’s important to remember that · need not be commutative, which the above glosses over: it’s perfectly possible that m?m? ? m?m?.)

看哪:这些事情(以及其他许多事物 - 实际上是"结合" 还是 "拣选"的数字倍增?)遵守相同的规则.拥有直觉对于发展理解很重要,但决定实际情况的是规则和定义.

最好的部分是这些直觉可以被同一个载体解释!设M是包含空集的一组集(不是一组所有集!),让是空集?,让我们成为联盟?很容易看出来?是?的身份,那?是关联的,所以我们可以得出结论:(M,?,?)是一个幺半群.现在:

  1. If we think about sets as being collections of things, then ? corresponds to glomming them together to get more things—the "combining" intuition.
  2. If we think about sets as representing possible actions, then ? corresponds to increasing your pool of possible actions to pick from—the "picking" intuition.

And this is exactly what’s going on with [] in Haskell: [a] is a Monoid for all a, and [] as an applicative functor (and monad) is used to represent nondeterminism. Both the combining and the picking intuitions coincide at the same type: mempty = empty = [] and mappend = (<|>) = (++).

So the Alternative class is just there to represent objects which (a) are applicative functors, and (b) when instantiated at a type, have a value and a binary function on them which follow some rules. Which rules? The monoid rules. Why? Because it turns out to be useful :-)


Why does Alternative need an empty method/member?

好吧,讽刺的回答是"因为Alternative代表了一个幺半群结构."但真正的问题是:为什么是一个幺半群结构?为什么不只是一个半群,一个没有幺半群?一个答案是声称幺半群只是更有用.我想很多人(但也许不是Edward Kmett)会同意这一点; 几乎所有的时间,如果你有一个明智的(<|>)/ mappend/·,你将能够定义一个明智的empty/ mempty/ .另一方面,具有额外的通用性是很好的,因为它可以让你在伞下放置更多的东西.

You also want to know how this meshes with the "picking" intuition. Keeping in mind that, in some sense, the right answer is "know when to abandon the 'picking’ intuition," I think you can unify the two. Consider [], the applicative functor for nondeterminism. If I combine two values of type [a] with (<|>), that corresponds to nondeterministically picking either an action from the left or an action from the right. But sometimes, you’re going to have no possible actions on one side—and that’s fine. Similarly, if we consider parsers, (<|>) represents a parser which parses either what’s on the left or what’s on the right (it "picks"). And if you have a parser which always fails, that ends up being an identity: if you pick it, you immediately reject that pick and try the other one.

所有这一切说,记住,这是完全有可能有一类几乎像Alternative,但缺乏empty.这将是完全有效的 - 它甚至可能是超类Alternative- 但恰好不是Haskell所做的.据推测,这不是一个有用的猜测.


为什么Alternative类型类需要Applicative约束,为什么它需要一种* -> *?...为什么不只是[使用] liftA2 mappend

Well, let’s consider each of these three proposed changes: getting rid of the Applicative constraint for Alternative; changing the kind of Alternative’s argument; and using liftA2 mappend instead of <|> and pure mempty instead of empty. We’ll look at this third change first, since it’s the most different. Suppose we got rid of Alternative entirely, and replaced the class with two plain functions:

fempty :: (Applicative f, Monoid a) => f a
fempty = pure mempty

(>|<) :: (Applicative f, Monoid a) => f a -> f a -> f a
(>|<) = liftA2 mappend
Run Code Online (Sandbox Code Playgroud)

We could even keep the definitions of some and many. And this does give us a monoid structure, it’s true. But it seems like it gives us the wrong one . Should Just fst >|< Just snd fail, since (a,a) -> a isn’t an instance of Monoid? No, but that’s what the above code would result in. The monoid instance we want is one that’s inner-type agnostic (to borrow terminology from Matthew Farkas-Dyck in a very related haskell-cafe discussion which asks some very similar questions); the Alternative structure is about a monoid determined by f’s structure, not the structure of f’s argument.

Now that we think we want to leave Alternative as some sort of type class, let’s look at the two proposed ways to change it. If we change the kind, we have to get rid of the Applicative constraint; Applicative only talks about things of kind * -> *, and so there’s no way to refer to it. That leaves two possible changes; the first, more minor, change is to get rid of the Applicative constraint but leave the kind alone:

class Alternative' f where
  empty' :: f a
  (<||>) :: f a -> f a -> f a
Run Code Online (Sandbox Code Playgroud)

The other, larger, change is to get rid of the Applicative constraint and change the kind:

class Alternative'' a where
  empty'' :: a
  (<|||>) :: a -> a -> a
Run Code Online (Sandbox Code Playgroud)

In both cases, we have to get rid of some/many, but that’s OK; we can define them as standalone functions with the type (Applicative f, Alternative' f) => f a -> f [a] or (Applicative f, Alternative'' (f [a])) => f a -> f [a].

Now, in the second case, where we change the kind of the type variable, we see that our class is exactly the same as Monoid (or, if you still want to remove empty'', Semigroup), so there’s no advantage to having a separate class. And in fact, even if we leave the kind variable alone but remove the Applicative constraint, Alternative just becomes forall a. Monoid (f a), although we can’t write these quantified constraints in Haskell, not even with all the fancy GHC extensions. (Note that this expresses the inner-type–agnosticism mentioned above.) Thus, if we can make either of these changes, then we have no reason to keep Alternative (except for being able to express that quantified constraint, but that hardly seems compelling).

So the question boils down to "is there a relationship between the Alternative parts and the Applicative parts of an f which is an instance of both?" And while there’s nothing in the documentation, I’m going to take a stand and say yes—or at the very least, there ought to be. I think that Alternative is supposed to obey some laws relating to Applicative (in addition to the monoid laws); in particular, I think those laws are something like

  1. Right distributivity (of <*>):  (f <|> g) <*> a = (f <*> a) <|> (g <*> a)
  2. Right absorption (for <*>):  empty <*> a = empty
  3. Left distributivity (of fmap):  f <$> (a <|> b) = (f <$> a) <|> (f <$> b)
  4. Left absorption (for fmap):  f <$> empty = empty

These laws appear to be true for [] and Maybe, and (pretending its MonadPlus instance is an Alternative instance) IO, but I haven’t done any proofs or exhaustive testing. (For instance, I originally thought that left distributivity held for <*>, but this "performs the effects" in the wrong order for [].) By way of analogy, though, it is true that MonadPlus is expected to obey similar laws (although there is apparently some ambiguity about which). I had originally wanted to claim a third law, which seems natural:

  • Left absorption (for <*>):  a <*> empty = empty

However, although I believe [] and Maybe obey this law, IO doesn’t, and I think (for reasons that will become apparent in the next couple of paragraphs) it’s best not to require it.

And indeed, it appears that Edward Kmett has some slides where he espouses a similar view; to get into that, we’ll need to take brief digression involving some more mathematical jargon. The final slide, "I Want More Structure," says that "A Monoid is to an Applicative as a Right Seminearring is to an Alternative," and "If you throw away the argument of an Applicative, you get a Monoid, if you throw away the argument of an Alternative you get a RightSemiNearRing."

Right seminearrings? "How did right seminearrings get into it?" I hear you cry. Well,

Definition. A right near-semiring (also right seminearring, but the former seems to be used more on Google) is a quadruple (R,+,·,0) where (R,+,0) is a monoid, (R,·) is a semigroup, and the following two conditions hold:

  1. · is right-distributive over +: for all r,s,t ? R, (s + t)r = sr + tr.
  2. 0 is right-absorbing for ·: for all r ? R, 0r = 0.

A left near-semiring is defined analogously.

Now, this doesn’t quite work, because <*> is not truly associative or a binary operator—the types don’t match. I think this is what Edward Kmett is getting at when he talks about "throw[ing] away the argument." Another option might be to say (I’m unsure if this is right) that we actually want (f a, <|>, <*>, empty) to form a right near-semiringoid, where the "-oid" suffix indicates that the binary operators can only be applied to specific pairs of elements (à la groupoids). And we’d also want to say that (f a, <|>, <$>, empty) was a left near-semiringoid, although this could conceivably follow from the combination of the Applicative laws and the right near-semiringoid structure. But now I’m getting in over my head, and this isn’t deeply relevant anyway.

At any rate, these laws, being stronger than the monoid laws, mean that perfectly valid Monoid instances would become invalid Alternative instances. There are (at least) two examples of this in the standard library: Monoid a => (a,) and Maybe. Let’s look at each of them quickly.

Given any two monoids, their product is a monoid; consequently, tuples can be made an instance of Monoid in the obvious way (reformatting the base package’s source):

instance (Monoid a, Monoid b) => Monoid (a,b) where
  mempty = (mempty, mempty)
  (a1,b1) `mappend` (a2,b2) = (a1 `mappend` a2, b1 `mappend` b2)
Run Code Online (Sandbox Code Playgroud)

Similarly, we can make tuples whose first component is an element of a monoid into an instance of Applicative by accumulating the monoid elements (reformatting the base package’s source):

instance Monoid a => Applicative ((,) a) where
  pure x = (mempty, x)
  (u, f) <*> (v, x) = (u `mappend` v, f x)
Run Code Online (Sandbox Code Playgroud)

However, tuples aren’t an instance of Alternative, because they can’t be—the monoidal structure over Monoid a => (a,b) isn’t present for all types b, and Alternative’s monoidal structure must be inner-type agnostic. Not only must b be a monad, to be able to express (f <> g) <*> a, we need to use the Monoid instance for functions, which is for functions of the form Monoid b => a -> b. And even in the case where we have all the necessary monoidal structure, it violates all four of the Alternative laws. To see this, let ssf n = (Sum n, (<> Sum n)) and let ssn = (Sum n, Sum n). Then, writing (<>) for mappend, we get the following results (which can be checked in GHCi, with the occasional type annotation):

  1. Right distributivity:
    • (ssf 1 <> ssf 1) <*> ssn 1 = (Sum 3, Sum 4)
    • (ssf 1 <*> ssn 1) <> (ssf 1 <*> ssn 1) = (Sum 4, Sum 4)
  2. Right absorption:
    • mempty <*> ssn 1 = (Sum 1, Sum 0)
    • mempty = (Sum 0, Sum 0)
  3. Left distributivity:
    • (<> Sum 1) <$> (ssn 1 <> ssn 1) = (Sum 2, Sum 3)
    • ((<> Sum 1) <$> ssn 1) <> ((<> Sum 1) <$> ssn 1) = (Sum 2, Sum 4)
  4. Left absorption:
    • (<> Sum 1) <$> mempty = (Sum 0, Sum 1)
    • mempty = (Sum 1, Sum 1)

Next, consider Maybe. As it stands, Maybe’s Monoid and Alternative instances disagree. (Although the haskell-cafe discussion I mention at the beginning of this section proposes changing this, there’s an Option newtype from the semigroups package which would produce the same effect.) As a Monoid, Maybe lifts semigroups into monoids by using Nothing as the identity; since the base package doesn’t have a semigroup class, it just lifts monoids, and so we get (reformatting the base package’s source):

instance Monoid a => Monoid (Maybe a) where
  mempty = Nothing
  Nothing `mappend` m       = m
  m       `mappend` Nothing = m
  Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
Run Code Online (Sandbox Code Playgroud)

On the other hand, as an Alternative, Maybe represents prioritized choice with failure, and so we get (again reformatting the base package’s source):

instance Alternative Maybe where
  empty = Nothing
  Nothing <|> r = r
  l       <|> _ = l
Run Code Online (Sandbox Code Playgroud)

And it turns out that only the latter satisfies the Alternative laws. The Monoid instance fails less badly than (,)’s; it does obey the laws with respect to <*>, although almost by accident—it comes form the behavior of the only instance of Monoid for functions, which (as mentioned above), lifts functions that return monoids into the reader applicative functor. If you work it out (it’s all very mechanical), you’ll find that right distributivity and right absorption for <*> all hold for both the Alternative and Monoid instances, as does left absorption for fmap. And left distributivity for fmap does hold for the Alternative instance, as follows:

f <$> (Nothing <|> b)
  = f <$> b                          by the definition of (<|>)
  = Nothing <|> (f <$> b)            by the definition of (<|>)
  = (f <$> Nothing) <|> (f <$> b)    by the definition of (<$>)

f <$> (Just a <|> b)
  = f <$> Just a                     by the definition of (<|>)
  = Just (f a)                       by the definition of (<$>)
  = Just (f a) <|> (f <$> b)         by the definition of (<|>)
  = (f <$> Just a) <|> (f <$> b)     by the definition of (<$>)
Run Code Online (Sandbox Code Playgroud)

However, it fails for the Monoid instance; writing (<>) for mappend, we have:

  • (<> Sum 1) <$> (Just (Sum 0) <> Just (Sum 0)) = Just (Sum 1)
  • ((<> Sum 1) <$> Just (Sum 0)) <> ((<> Sum 1) <$> Just (Sum 0)) = Just (Sum 2)

Now, there is one caveat to this example. If you only require that Alternatives be compatibility with <*>, and not with <$>, then Maybe is fine. Edward Kmett’s slides, mentioned above, don’t make reference to <$>, but I think it seems reasonable to require laws with respect to it as well; nevertheless, I can’t find anything to back me up on this.

Thus, we can conclude that being an Alternative is a stronger requirement than being a Monoid, and so it requires a different class. The purest example of this would be a type with an inner-type agnostic Monoid instance and an Applicative instance which were incompatible with each other; however, there aren’t any such types in the base package, and I can’t think of any. (It’s possible none exist, although I’d be surprised.) Nevertheless, these inner-type gnostic examples demonstrate why the two type classes must be different.


What’s the point of the MonadPlus type class?

MonadPlus, like Alternative, is a strengthening of Monoid, but with respect to Monad instead of Applicative. According to Edward Kmett in his answer to the question "Distinction between typeclasses MonadPlus, Alternative, and Monoid?", MonadPlus is also stronger than Alternative: the law empty <*> a, for instance, doesn’t imply that empty >>= f. AndrewC provides two examples of this: Maybe and its dual. The issue is complicated by the fact that there are two potential sets of laws for MonadPlus. It

  • 我喜欢歇斯底里的葡萄干. (2认同)
  • 从2015年开始:Alternative是MonadPlus的超类http://hackage.haskell.org/package/base-4.8.0.0/docs/Control-Monad.html#t:MonadPlus (2认同)

And*_*ewC 16

import Data.Monoid
import Control.Applicative
Run Code Online (Sandbox Code Playgroud)

让我们来看一下Monoid和Alternative如何与仿Maybe函数和仿函数进行交互的示例ZipList,但让我们从头开始,部分是为了让所有的定义在我们的脑海中清新,部分是为了不再将标签切换到一些hackage,而是主要是因为我可以运行这个过去的ghci来纠正我的错别字!

(<>) :: Monoid a => a -> a -> a
(<>) = mappend -- I'll be using <> freely instead of `mappend`.
Run Code Online (Sandbox Code Playgroud)

这是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)
Run Code Online (Sandbox Code Playgroud)

现在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)   -- zip them up, applying the fs to the xs
   pure a = Zip (repeat a)   -- infinite so that when you zip with something, lengths don't change
Run Code Online (Sandbox Code Playgroud)

结构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
Run Code Online (Sandbox Code Playgroud)

连接本身就是在String级别,而不是真正的可能级别,通过将其No视为一样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
Run Code Online (Sandbox Code Playgroud)

这种幺半群结构Perhaps试图尽可能地在a水平上工作.注意Monoid a约束,告诉我们我们正在使用层次结构a.这不是一个替代结构,它是一个派生(提升)的Monoid结构.

instance Monoid a => Monoid (Perhaps a) where
   mappend = (<++>)
   mempty = No
Run Code Online (Sandbox Code Playgroud)

在这里,我使用数据a的结构来为整个事物添加结构.如果我正在组合Sets,我将能够添加Ord a上下文.

ZipList克隆

那么我们应该如何将元素与zipList 结合起来呢?如果我们将它们组合起来,它们应该用什么拉链?

   Zip ["HELLO","MUM","HOW","ARE","YOU?"] 
<> Zip ["this", "is", "fun"]
=  Zip ["HELLO" ? "this",   "MUM" ? "is",   "HOW" ? "fun"]

mempty = ["","","","",..]   -- sensible zero element for zipping with ?
Run Code Online (Sandbox Code Playgroud)

但是我们应该用什么呢?.我说这里唯一明智的选择是++.实际上,对于列表,(<>) = (++)

   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, .....]  -- sensible zero element
Run Code Online (Sandbox Code Playgroud)

但是我们可以使用什么才能?说我们的意思是组合元素,所以我们应该再次使用Monoid的元素组合运算符:<>.

instance Monoid a => Monoid (Zip a) where
   Zip as `mappend` Zip bs = Zip (zipWith (<>) as bs) -- zipWith the internal mappend
   mempty = Zip (repeat mempty)  -- repeat the internal mempty
Run Code Online (Sandbox Code Playgroud)

这是使用拉链组合元素的唯一合理方式 - 因此它是唯一合理的幺半群实例.

有趣的是,这对上面的Maybe示例不起作用,因为Haskell不知道如何组合Ints - 它应该使用+还是*?要获取数字数据的Monoid实例,可以将它们包装SumProduct告诉它使用哪个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]
Run Code Online (Sandbox Code Playgroud)

关键点

注意 Monoid中的类型有种类*正是允许我们在Monoid a这里放置上下文的事实- 我们也可以添加Eq aOrd a.在Monoid中,原始元素很重要.Monoid实例旨在让您操作和组合结构内的数据.

结构2:更高层次的选择:替代方案

选择运算符类似,但也不同.

也许克隆

(<||>) :: Perhaps String -> Perhaps String -> Perhaps String
Yes xs <||> Yes ys = Yes xs   -- if we can have both, choose the left one
Yes xs <||> No     = Yes xs
No     <||> Yes ys = Yes ys
No     <||> No     = No  
Run Code Online (Sandbox Code Playgroud)

这里没有连接 - 我们根本没有使用++- 这个组合纯粹在这个Perhaps级别上工作,所以让我们改变类型签名到

(<||>) :: Perhaps a -> Perhaps a -> Perhaps a
Yes xs <||> Yes ys = Yes xs   -- if we can have both, choose the left one
Yes xs <||> No     = Yes xs
No     <||> Yes ys = Yes ys
No     <||> No     = No  
Run Code Online (Sandbox Code Playgroud)

请注意,没有约束 - 我们没有使用a级别的结构,只是级别的结构Perhaps.这是一种替代结构.

instance Alternative Perhaps where
   (<|>) = (<||>)
   empty = No  
Run Code Online (Sandbox Code Playgroud)

ZipList克隆

我们应该如何在两个拉链列表之间进行选择?

Zip [1,3,4] <|> Zip [10,20,30,40] = ????
Run Code Online (Sandbox Code Playgroud)

<|>在元素上使用会非常诱人,但我们不能,因为我们无法使用元素的类型.让我们从开始吧empty.它不能使用元素,因为我们在定义Alternative时不知道元素的类型,所以它必须是Zip [].我们需要它是一个左(并且最好是正确)的身份<|>,所以

Zip [] <|> Zip ys = Zip ys
Zip xs <|> Zip [] = Zip xs
Run Code Online (Sandbox Code Playgroud)

有两个合理的选择Zip [1,3,4] <|> Zip [10,20,30,40]:

  1. Zip [1,3,4] 因为它是第一个 - 与Maybe一致
  2. Zip [10,20,30,40]因为它是最长的 - 与Zip []丢弃一致

嗯,这很容易决定:因为pure x = Zip (repeat x),两个列表可能都是无限的,所以比较它们的长度可能永远不会终止,所以它必须选择第一个.因此,唯一明智的替代实例是:

instance Alternative Zip where
   empty = Zip []
   Zip [] <|> x = x
   Zip xs <|> _ = Zip xs
Run Code Online (Sandbox Code Playgroud)

这是我们可以定义的唯一合理的替代方案.请注意它与Monoid实例的不同之处,因为我们无法弄乱元素,我们甚至无法查看它们.

关键点

请注意,由于Alternative需要类型的构造* -> *没有可能的方式来添加一个Ord aEq aMonoid a上下文.不允许 Alternative 使用有关结构内数据的任何信息.无论你想要多少,都不能对数据任何事情,除非可能扔掉它.

关键点:Alternative和Monoid之间有什么区别?

不是很多 - 它们都是幺半群,但总结最后两节:

Monoid *实例可以组合内部数据.Alternative (* -> *)实例让它变得不可能.Monoid提供灵活性,Alternative提供保证.种类*,(* -> *)是这种差异的主要驱动力.拥有它们都允许您使用两种操作.

这是正确的,我们的两种口味都是合适的.Monoid实例Perhaps String表示将所有字符放在一起,Alternative实例表示字符串之间的选择.

对于可能的Monoid实例没有任何问题 - 它正在完成它的工作,结合数据.
对于Maybe的Alternative实例没有任何问题 - 它正在做它的工作,在事物之间做出选择.

Zip的Monoid实例结合了它的元素.Zip的Alternative实例被迫选择其中一个列表 - 第一个非空列表.

能够做到这两点是件好事.

什么是适用环境用于什么?

选择和应用之间存在一些互动.请参阅Antal SZ在他的问题中或在他的答案中陈述的法律.

从实际的角度来看,它很有用,因为Alternative是用于某些Applicative Functors的选择.该功能被用于Applicatives,因此发明了一般的接口类.Applicative Functors适用于表示产生值的计算(IO,Parser,输入UI元素......),其中一些必须处理失败 - 需要替代方案.

为什么备选方案有empty

为什么Alternative需要一个空方法/成员?我可能错了,但似乎根本没有使用...至少在我能找到的代码中.它似乎不适合与类的主题 - 如果我有两件事情,并且需要选择一个,我需要什么"空"的呢?

这就像问为什么添加需要0 - 如果你想添加东西,有什么东西不添加任何东西有什么意义?答案是0是crucual举足轻重的数量在其周围一切都围绕此外,就像1是乘法的关键,[]是对列表的关键(和y=e^x是至关重要的微积分).实际上,您使用这些do-nothing元素来开始构建:

sum = foldr (+) 0
concat = foldr (++) []
msum = foldr (`mappend`) mempty          -- any Monoid
whichEverWorksFirst = foldr (<|>) empty  -- any Alternative
Run Code Online (Sandbox Code Playgroud)

我们不能用Monad + Alternative取代MonadPlus吗?

MonadPlus类型的重点是什么?我不能通过使用Monad和Alternative等东西来解锁所有的善良吗?为什么不放弃呢?(我确定我错了,但我没有任何反例)

你没错,没有任何反例!

你有趣的问题是Antal SZ,PetrPudlák,我深入研究了MonadPlus和Applicative之间的关系.答案, 这里这里 是任何一个MonadPlus(在左分布意义上 - 跟随链接的细节)也是一个Alternative,但不是相反.

这意味着如果您创建Monad和MonadPlus的实例,它无论如何都满足Applicative和Alternative的条件.这意味着如果您遵循MonadPlus的规则(使用left dist),您也可以使Monad成为一个应用程序并使用Alternative.

但是,如果我们删除MonadPlus类,我们会删除一个合理的位置来记录规则,并且你无法指定某些东西的替代品而不是MonadPlus(技术上我们应该为Maybe做过).这些是理论上的原因.实际的原因是它会破坏现有的代码.(这也是为什么Applicative和Functor都不是Monad的超类.)

不是Alternative和Monoid一样吗?不是Alternative和Monoid完全不同吗?

'pedia说"替代类型类适用于具有幺半群结构的Applicative仿函数." 我不明白 - 不是替代意味着与Monoid完全不同的东西吗?也就是说,我理解了替代类型类在两件事之间的选择,而我理解Monoids是关于组合事物.

Monoid和Alternative是以合理的方式从一个对象中获取一个对象的两种方法.数学并不关心你是在选择,组合,混合还是炸毁你的数据,这就是为什么Alternative被称为Monoid for Applicative.你现在似乎在家里有这个概念,但你现在说

对于同时具有Alternative和Monoid实例的类型,实例应该是相同的

我不同意这一点,我认为我的Maybe和ZipList示例都会被仔细解释为什么它们不同.如果有的话,我认为它们是相同的很少见.我只能想到一个例子,简单列表,这是合适的.这是因为列表是monoid的基本示例++,但是列表在某些上下文中用作元素的不确定选择,所以<|>也应该如此++.


And*_*ewC 8

摘要

  • 我们需要定义(提供相同操作的实例)一些应用仿函数的Monoid实例,它们在应用仿函数级别真正组合,而不仅仅是提升低级别的monoid.下面的示例错误litvar = liftA2 mappend literal variable显示<|>通常不能定义为liftA2 mappend; <|>在这种情况下,通过组合解析器而不是它们的数据.

  • 如果我们直接使用Monoid,我们需要语言扩展来定义实例.Alternative是更高的kinded所以你可以制作这些实例,而无需语言扩展.

示例:解析器

让我们假设我们正在解析一些声明,因此我们导入了我们需要的所有内容

import Text.Parsec
import Text.Parsec.String
import Control.Applicative ((<$>),(<*>),liftA2,empty)
import Data.Monoid
import Data.Char
Run Code Online (Sandbox Code Playgroud)

并考虑我们将如何解析一个类型.我们选择简单化:

data Type = Literal String | Variable String  deriving Show
examples = [Literal "Int",Variable "a"]
Run Code Online (Sandbox Code Playgroud)

现在让我们为文字类型编写一个解析器:

literal :: Parser Type
literal = fmap Literal $ (:) <$> upper <*> many alphaNum
Run Code Online (Sandbox Code Playgroud)

含义:解析uppercase字符,然后解析many alphaNumeric字符,将结果与纯函数组合成一个String (:).然后,应用纯函数LiteralStrings转换为Types.我们将以完全相同的方式解析变量类型,除了以lower大小写字母开头:

variable :: Parser Type
variable = fmap Variable $ (:) <$> lower <*> many alphaNum
Run Code Online (Sandbox Code Playgroud)

这很棒,而且parseTest literal "Bool" == Literal "Bool"正如我们所希望的那样.

问题3a:如果要将应用效果与Monoid的行为结合起来,为什么不呢 liftA2 mappend

编辑:哎呀 - 忘了实际使用<|>!
现在让我们使用Alternative组合这两个解析器:

types :: Parser Type
types = literal <|> variable
Run Code Online (Sandbox Code Playgroud)

这可以解析任何Type:parseTest types "Int" == Literal "Bool"parseTest types "a" == Variable "a".这组合了两个解析器,而不是两个.这就是它在Applicative Functor级别而不是数据级别上运行的意义.

但是,如果我们尝试:

litvar = liftA2 mappend literal variable
Run Code Online (Sandbox Code Playgroud)

这将要求编译器在数据级别组合它们生成的两个.我们得到了

No instance for (Monoid Type)
  arising from a use of `mappend'
Possible fix: add an instance declaration for (Monoid Type)
In the first argument of `liftA2', namely `mappend'
In the expression: liftA2 mappend literal variable
In an equation for `litvar':
    litvar = liftA2 mappend literal variable
Run Code Online (Sandbox Code Playgroud)

所以我们发现了第一件事; Alternative类做了一些真正不同的东西liftA2 mappend,因为它组合了不同级别的对象 - 它结合了解析器,而不是解析的数据.如果你想以这种方式思考它,那就是真正更高级别的组合,而不仅仅是电梯.我不喜欢这样说,因为Parser Type有点好*,但是说我们把Parsers而不是Types 组合起来是真的.

(即使对于具有Monoid实例的类型,liftA2 mappend也不会给你相同的解析器<|>.如果你尝试它,Parser String你会得到liftA2 mappend一个接一个地解析然后连接,相比之下<|>将尝试第一个解析器,默认为第二个如果它失败了.)

问题3b:替代品<|> :: f a -> f a -> f a与Monoid的不同之处是mappend :: b -> b -> b什么?

首先,您应该注意到它没有提供Monoid实例的新功能.

其次,直接使用Monoid存在一个问题:让我们尝试mappend在解析器上使用,同时显示它与以下结构相同Alternative:

instance Monoid (Parser a) where
    mempty = empty
    mappend = (<|>)
Run Code Online (Sandbox Code Playgroud)

哎呀!我们得到了

Illegal instance declaration for `Monoid (Parser a)'
  (All instance types must be of the form (T t1 ... tn)
   where T is not a synonym.
   Use -XTypeSynonymInstances if you want to disable this.)
In the instance declaration for `Monoid (Parser a)'
Run Code Online (Sandbox Code Playgroud)

因此,如果您有一个applicative functor f,那么Alternative实例会显示它f a是一个monoid,但您只能将其声明为Monoid具有语言扩展名.

一旦我们添加{-# LANGUAGE TypeSynonymInstances #-}到文件的顶部,我们就可以了,并且可以定义

typeParser = literal `mappend` variable
Run Code Online (Sandbox Code Playgroud)

令我们高兴的是,它有效:parseTest typeParser "Yes" == Literal "Yes"parseTest typeParser "a" == Literal "a".

即使你没有任何同义词(Parser并且String是同义词,所以它们已经出局),你仍然需要{-# LANGUAGE FlexibleInstances #-}定义像这样的实例:

data MyMaybe a = MyJust a | MyNothing deriving Show
instance Monoid (MyMaybe Int) where
   mempty = MyNothing
   mappend MyNothing x = x
   mappend x MyNothing = x
   mappend (MyJust a) (MyJust b) = MyJust (a + b)
Run Code Online (Sandbox Code Playgroud)

(Maybe的monoid实例通过提升底层的monoid来解决这个问题.)

使标准库不必要地依赖于语言扩展显然是不可取的.


所以你有它.替代方案只是Monoid for Applicative Functors(并不仅仅是Monoid的提升).它需要更高级的类型,f a -> f a -> f a因此您可以定义一个没有语言扩展的类型.

您的其他问题,为了完整性:

  1. 为什么Alternative需要一个空方法/成员?
    因为拥有操作的身份有时是有用的.例如,您可以在anyA = foldr (<|>) empty不使用繁琐的边缘情况的情况下进行定义.

  2. MonadPlus类型的重点是什么?我不能通过使用Monad和Alternative等东西来解锁所有的善良吗?不,我推荐您回到您链接问题:

而且,即使Applicative是Monad的超类,你最终还是需要MonadPlus课程,因为服从empty <*> m = empty并不足以证明这一点empty >>= f = empty.

....我想出了一个例子:也许吧.我详细解释,在答案中证明了 安塔尔的问题.出于这个答案的目的,值得注意的是我能够使用>> =使MonadPlus实例破坏了Alternative法则.


幺半群结构很有用.替代方案是为Applicative Functors提供它的最佳方式.


Mat*_*ick 4

我不会讨论 MonadPlus,因为对其法律存在分歧。


在尝试找到任何有意义的示例(其中 Applicative 的结构自然会导致与其 Monoid 实例不一致的替代实例*)失败后,我终于想出了这个:

Alternative 的法则比 Monoid 的法则更严格,因为结果不能依赖于内部类型。这将大量 Monoid 实例排除在替代方案之外。 这些数据类型允许部分(意味着它们仅适用于某些内部类型)Monoid 实例,这是此类额外“结构”所禁止的* -> *。例子:

  • Monoid 的标准 Maybe 实例假设内部类型是 Monoid => 不是 Alternative

  • ZipList、元组和函数都可以成为 Monoids,如果它们的内部类型是 Monoids => not Alternatives

  • 至少有一个元素的序列 - 不能是替代品,因为没有empty

    data Seq a
        = End a
        | Cons a (Seq a)
      deriving (Show, Eq, Ord)
    
    Run Code Online (Sandbox Code Playgroud)

另一方面,某些数据类型不能成为替代品,因为它们是*-kinded 的:

  • 单元 - ()
  • Ordering
  • 数字、布尔值

我推断的结论: 对于同时具有替代实例和 Monoid 实例的类型,这些实例应该是相同的。 另请参阅此答案


排除 Maybe,我认为这不算数,因为它的标准实例不应该要求 Monoid 作为内部类型,在这种情况下,它将与 Alternative 相同