困惑于'Alternative'types的含义及其与其他types的关系

我一直在通过Typeclassopedia来学习types类。 我坚持了解Alternative (和MonadPlus ,就此而言)。

我遇到的问题是:

  • “pedia说:”替代types是用于应用函子,它也有一个monoid结构。“ 我没有得到这个 – 不是替代意味着什么完全不同于Monoid? 即我理解了Alternative类的要点是select两个事物,而我理解Monoid是关于组合事物。

  • 为什么select需要一个empty方法/成员? 我可能是错的,但似乎根本没有被使用…至less在我能find的代码中 。 它似乎不符合课程的主题 – 如果我有两件事情,需要select一个,我需要一个“空”的东西?

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

  • MonadPlustypes类的重点是什么? 我不能仅仅使用MonadAlternative东西来解锁它的所有优点吗? 为什么不只是沟渠呢? (我确定我错了,但是我没有任何反例)

希望所有这些问题都是一致的…!


赏金更新:@Antal的答案是一个很好的开始,但是Q3仍然是开放的:Alternative提供的Monoid不是什么? 我觉得这个答案不能令人满意,因为它没有具体的例子,并且对Alternative的较高主观性如何区分它与Monoid有一个具体的讨论。

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

 liftA2 mappend 

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

这就是为什么我正在寻找具体的例子 ,说明为什么select是必要的,以及它与Monoid有什么不同,或者说有什么不同。

首先,让我简单回答一下这些问题。 然后,我将把每一个扩展到一个更长的详细答案,但是这些短的答案将有望帮助你浏览这些答案。

  1. 不, AlternativeMonoid并不意味着不同的事物; Alternative是具有MonoidMonoid结构的types。 “采摘”和“结合”是同一个更广泛的概念的两种不同的直觉。

  2. Alternative包含empty以及<|>因为devise者认为这将是有用的,因为这会产生一个monoid。 在采摘方面, empty对应于做出不可能的select。

  3. 我们既需要Alternative又需要更多的法则,因为前者要比后者服从(或应该) 更多的法则; 这些法则涉及types构造函数的monoidal和applicative结构。 另外, Alternative不能依赖于内部types,而Monoid可以。

  4. MonadPlusAlternative更强大,因为它必须遵守更多的法律; 除了应用结构之外,这些定律还将monoidal结构与monadic结构联系起来。 如果你有两个实例,他们应该重合。


Alternative意味着与Monoid完全不同的东西吗?

不是真的! 造成混淆的部分原因是Haskell Monoid类使用了一些非常糟糕的(好的,不够一般的)名字。 这是一个math家如何定义一个monoid(对此非常明确):

定义。 一个幺半群是一个具有一个特征元素ε∈M和一个二元运算符MM × MM的集合M ,并列表示,以下两个条件成立:

  1. ε是同一性:对于所有的m∈M, = εm = m
  2. 对于所有的m 1, m 2, m 3∈M,( m 1 m 2) m 3 = m 1( m 2 m 3)是相联的。

而已。 在Haskell中,ε被拼写为mempty并且拼写为mappend (或者,这些日子<> ),并且集合Minstance Monoid M where ...的typesMinstance Monoid M where ...

看这个定义,我们看到它没有提到“组合”(或关于“挑选”)。 它说关于ε和关于ε的事情,但就是这样。 现在,把这些东西结合起来确实是合适的: ε对应于没有东西, m 1 m 2表示如果我把m 1和m 2的东西放在一起,我可以得到一个包含所有东西的新东西。 但是,这里有一个可供select的直觉: ε完全没有select, m 1 m 2对应于m 1和m 2之间的select。 这是“挑选”的直觉。 请注意,两者都服从幺半律:

  1. 一无所有,别无select都是这个身份。
    • 如果我没有东西,并且把它和一些东西一起扔掉,我会再次得到同样的东西。
    • 如果我有一个select(不可能)和其他select之间的select,我必须select其他(可能的)select。
  2. Glomming集合在一起,并作出select都是联想。
    • 如果我有三件东西的话,先把两件一起,然后再把第三件,最后两件放在一起,然后把第一件放在一起,这并不重要。 无论如何,我最终都会得到相同的整体收集。
    • 如果我在三件事情中有一个select,那么我是否(a)首先在一,二和三之间进行select并且如果我需要在一,二之间进行select并不重要,或者(b)第二或第三,然后,如果我需要,第二和第三。 无论哪种方式,我可以select我想要的。

(注意:我在这里玩得很快,很松,这就是为什么它是直觉,例如,重要的是要记住:不必是可交换的,上面的结论是可以的: m 1 m 2≠ m 2 m 1 。)

看哪:这些东西(还有其他许多东西 – 数量的增加实际上是“结合” 还是 “挑选”?)遵守相同的规则。 有了直觉对于理解是很重要的,但是确定实际情况的是规则和定义。

而最好的部分是,这两种直觉都可以由同一个承运人来解释! 假设M是包含空集的一组集合(不是所有集合的集合),令ε为空集合,令集合∪为集合。 很容易看出∅是∪的一个同一性,∪是关联的,所以我们可以得出结论( M ,∅,∪)是幺半群。 现在:

  1. 如果我们把集合看作是事物的集合,那么∪对应于把它们组合在一起以获得更多的东西 – “合并”直觉。
  2. 如果我们把集合看作是表示可能的行为,那么∪对应于增加你从“挑选”直觉中select的可能行动的集合。

而这正是Haskell中的[]所做的: [a]是所有aMonoid ,而[]作为应用函数(和monad)被用来表示非确定性。 组合和挑选的直觉符合相同的types: mempty = empty = []mappend = (<|>) = (++)

所以Alternative类就是在那里表示(a)是应用函子的对象,(b)当在一个types上实例化时,在它们上有一个值和一个二元函数,遵循一些规则。 哪些规则? 幺点规则。 为什么? 因为它变得有用:-)


为什么Alternative需要一个空的方法/成员?

那么,这个晦涩的答案是“因为Alternative是一个幺半群结构”。但真正的问题是: 为什么是一个幺半群结构呢? 为什么不只是一个半群,一个没有ε的幺半群呢? 一个答案是声称monoids只是更有用。 我想很多人(但也许不是爱德华·凯米特 )会同意这一点; 几乎所有的时候,如果你有一个明智的(<|>) / mappend /·,你将能够定义一个合理的empty / mempty / ε 。 另一方面,多余的普遍性是好的,因为它可以让你把更多的东西放在伞下。

你也想知道这是如何与“挑选”直觉相吻合的。 记住,从某种意义上说,正确的答案是“知道什么时候放弃'select'的直觉,”我认为你可以统一这两个。 考虑[] ,非确定性的应用函子。 如果我将[a]types的两个值与(<|>)组合在一起,这对应于非确定性select左边的动作或右边的动作。 但是有时候,你将不会有任何可能的行动 – 这很好。 同样,如果我们考虑parsing器, (<|>)代表一个parsing器,parsing左边或右边的内容(“select”)。 如果你有一个总是失败的parsing器,那么这个parsing器最终会成为一个身份:如果你select它,你立即拒绝那个select,然后尝试另一个。

所有这一切,请记住,完全可以有一个类似“ Alternative ,但缺乏empty 。 这将是完全有效的 – 甚至可以是Alternative的超类 – 但并不是Haskell做的。 据推测,这是没有什么有用的猜测。


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

那么,让我们考虑这三个build议的变化:摆脱AlternativeApplicative约束; 改变Alternative的论点; 并使用liftA2 mappend而不是<|>pure mempty而不是empty 。 我们先看第三个变化,因为它是最不同的。 假设我们完全摆脱了“ Alternative ,并用两个简单的函数replace了类:

 fempty :: (Applicative f, Monoid a) => fa fempty = pure mempty (>|<) :: (Applicative f, Monoid a) => fa -> fa -> fa (>|<) = liftA2 mappend 

我们甚至可以保留somemany的定义。 这确实给了我们一个monoid结构,这是真的。 但它似乎给了我们一个错误的。 因为(a,a) -> a不是Monoid一个实例吗? 不,但是这就是上面的代码会导致的结果。我们想要的monoid实例是一个内在types不可知论者(借用Matthew Farkas-Dyck的术语,在一个非常相关的haskell-cafe讨论中提出了一些非常类似的问题)。 Alternative结构是由f的结构决定的一个幺半群,而不是f的论证结构。

现在我们认为我们要把Alternative作为某种types的类,让我们来看看两种提议的方法来改变它。 如果我们改变这种方式,就必须摆脱Applicative限制; Applicative只会谈论类似的东西* -> * ,所以没有办法引用它。 这留下了两个可能的变化; 第一个更小的改变就是摆脱了Applicative限制,但是只保留这种限制:

 class Alternative' f where empty' :: fa (<||>) :: fa -> fa -> fa 

另一个更大的变化是摆脱适用性约束,改变种类:

 class Alternative'' a where empty'' :: a (<

>) :: a -> a -> a

在这两种情况下,我们必须摆脱some / many ,但没关系; 我们可以将它们定义为独立函数,其types为(Applicative f, Alternative' f) => fa -> f [a](Applicative f, Alternative'' (f [a])) => fa -> f [a]

现在,在第二种情况下,在我们改变typesvariables的types的时候,我们看到我们的类和Monoid完全一样(或者,如果你还想删除empty'' Semigroup ),所以没有什么好处一个单独的类。 而事实上,即使我们单独留下那种variables,但删除了“ Applicative约束,“ Alternative也变成forall a. Monoid (fa) forall a. Monoid (fa) ,尽pipe我们不能在Haskell中编写这些量化的约束条件,甚至不能用所有奇特的GHC扩展。 (注意,这表示了上面提到的内在型不可知论)。因此,如果我们能够做出这些变化,那么我们就没有理由保留Alternative (除了能够expression那种量化的约束,但是这似乎很难引起注意)。

所以这个问题归结为“ Alternative部分和fApplicative部分之间是否有一个关系,这是两者的一个实例?”尽pipe文档中没有任何内容,我将采取立场并说 -或者至less应该有。 我认为Alternative应该遵守与适用有关的一些法律(除了幺半律之外)。 特别是我认为这些法律是类似的

  1. <*> )的右分配性: (f <|> g) <*> a = (f <*> a) <|> (g <*> a)
  2. 正确的吸收(用于<*> ): empty <*> a = empty
  3. 左分配( fmap ): f <$> (a <|> b) = (f <$> a) <|> (f <$> b)
  4. 左吸收(对于fmap ): f <$> empty = empty

这些法律对于[]Maybe似乎是正确的,并且(假装它的MonadPlus实例是Alternative实例) IO ,但是我没有做任何certificate或者详尽的testing。 (例如,我原本以为剩余的分配为<*> ,但是这个“按照[]的错误顺序执行效果” [] 。但是,通过类比, MonadPlus确实会遵守类似的法则(虽然显然有些含糊不清 )。 我本来想要宣称第三条法律,这看起来很自然:

  • 左吸收(对于<*> ): a <*> empty = empty

然而,尽pipe我相信[][] Maybe服从这个规律, IO没有,我想(因为在接下来的几段中将会变得明显的原因),最好不要求它。

事实上,爱德华·凯梅特(Edward Kmett)似乎有一些幻灯片 ,他赞同类似的观点。 为了解决这个问题,我们需要简单地进行一些涉及更多math术语的解释。 最后一张幻灯片“我想要更多的结构”说:“一个幺半群是一个适用于一个正确的半方向是另一种方式”,“如果抛弃应用的论点,你会得到一个幺半群,如果你扔抛开一个select的论点,你会得到一个RightSemiNearRing。“

正确的seminearrings? “正确的seminearrings如何进入?” 我听到你哭了。 好,

定义。R ,+,0)是一个幺半群,( R ,·)是右四半 (也是右半直线 ,但前者似乎在Google上被更多地使用)是一个半群,下列两个条件成立:

  1. ·对于所有的rs ,t∈R,( s + tr = sr + tr +右分布的。
  2. 0对于所有r∈R,0 r = 0是右吸收的。

类似地定义左近半球

现在,这并不奏效,因为<*>不是真正的关联或二元运算符 – types不匹配。 我认为这就是爱德华·科梅特在谈论“抛弃论点”时所说的话。另一个select可能是我们实际上想要的(我不确定这是否正确)( fa<|><*>empty )形成一个右近似半体 ,其中“类”后缀表示二元运算符只能应用于特定的元素对( )。 而且我们也想说( fa<|><$>empty )是一个左近似empty ,尽pipe这可以从Applicative规律和右近半结构的结合中得出。 但是现在我已经越过了自己的脑袋,而且这也不是很重要。

无论如何,这些法律于幺半律,意味着完全有效的Monoid实例将成为无效的Alternative实例。 在标准库中至less有两个这样的例子: Monoid a => (a,)Maybe 。 让我们来看看他们每一个很快。

鉴于任何两个monoid,他们的产品是monoid; 因此,元组可以以明显的方式作为Monoid一个实例(重新设置基本包的源代码 ):

 instance (Monoid a, Monoid b) => Monoid (a,b) where mempty = (mempty, mempty) (a1,b1) `mappend` (a2,b2) = (a1 `mappend` a2, b1 `mappend` b2) 

同样,我们可以通过累积monoid元素(重新设置基本包的源代码 ),将第一个元素是monoid元素的元组变成Applicative的实例:

 instance Monoid a => Applicative ((,) a) where pure x = (mempty, x) (u, f) <*> (v, x) = (u `mappend` v, fx) 

然而,元组不是一个Alternative的实例,因为它们不可能是Monoid a => (a,b)上的monoidal结构,对于所有typesb都是不存在的,而Alternative的monoidal结构必须是inner-types不可知论者。 不仅是一个单子,为了能够expression(f <> g) <*> a ,我们需要为函数使用Monoid实例,这个函数是Monoid b => a -> b函数。 即使在我们拥有所有必要的monoidal结构的情况下,也违反了所有四种 Alternative法则。 为了看到这个,让ssf n = (Sum n, (<> Sum n))并让ssn = (Sum n, Sum n) 。 然后,为mappend(<>) ,我们得到以下结果(可以在GHCi中检查,偶尔使用types注释):

  1. 正确的分配:
    • (ssf 1 <> ssf 1) <*> ssn 1 = (Sum 3, Sum 4)
    • (ssf 1 <*> ssn 1) <> (ssf 1 <*> ssn 1) = (Sum 4, Sum 4)
  2. 正确的吸收:
    • mempty <*> ssn 1 = (Sum 1, Sum 0)
    • mempty = (Sum 0, Sum 0)
  3. 左分配:
    • (<> Sum 1) <$> (ssn 1 <> ssn 1) = (Sum 2, Sum 3)
    • ((<> Sum 1) <$> ssn 1) <> ((<> Sum 1) <$> ssn 1) = (Sum 2, Sum 4)
  4. 左吸收:
    • (<> Sum 1) <$> mempty = (Sum 0, Sum 1)
    • mempty = (Sum 1, Sum 1)

接下来,考虑Maybe 。 就目前而言, MaybeMonoidAlternative实例不一致 。 (虽然我在本节开头提到的haskell-cafe讨论提出改变这个,但是从semigroups包中有一个Option newtype可以产生相同的效果。)作为一个MonoidMaybe通过使用Nothing作为身份提升半群; 由于基础包没有半群类,所以它只是提升monoids,所以我们得到(重新格式化基础包的源代码 ):

 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) 

另一方面,作为一种AlternativeMaybe代表了优先select失败,所以我们得到(再次重新格式化基础包的源代码 ):

 instance Alternative Maybe where empty = Nothing Nothing <|> r = r l <|> _ = l 

事实certificate,只有后者符合Alternative法则。 Monoid实例比(,)的失败要less得多。 它确实遵守关于<*>的规律,虽然几乎是偶然的,但是它形成了函数的唯一Monoid实例的行为,(如上所述)提升了将monoid返回到读写器应用函子的函数。 如果你解决了这个问题(这一切都是非常机械的),那么你会发现,对于AlternativeMonoid实例来说,正确的分布性和正确的吸收都适用于fmapfmap左派分配确实适用于Alternative实例,如下所示:

 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 (fa) by the definition of (<$>) = Just (fa) <|> (f <$> b) by the definition of (<|>) = (f <$> Just a) <|> (f <$> b) by the definition of (<$>) 

但是,对于Monoid实例来说却失败了。 为mappend(<>) ,我们有:

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

现在,这个例子有一个警告。 如果你只要求Alternative方法与<*>兼容,而不是<$> ,那么Maybe也可以。 上面提到的爱德华·凯米特的幻灯片并没有提及<$> ,但我认为似乎也需要有关法律的合理性。 不过,我找不到任何东西来支持我。

因此,我们可以得出结论,作为一个Alternative是一个比作为一个Monoid 更强大的要求,所以它需要一个不同的阶级。 这个最简单的例子就是一个内部types不可知的Monoid实例和一个Applicative实例,它们是不相容的。 然而,基础包里没有这样的types,我想不出来。 (尽pipe我会感到惊讶,但是这些可能都不存在。)然而,这些内在型诺斯替例子certificate了为什么这两种types必须有所不同。


MonadPlustypes的重点是什么?

MonadPlus就像Alternative一样,是Monoid的强化,但Monad而不是Applicative 。 根据Edward Kmett在他对“ MonadPlusAlternativeMonoid之间的区别”这个问题的回答 , MonadPlus Alternative更强大:例如, empty >>= f 。 AndrewC提供了两个例子: Maybe和它的双重性。 这个问题很复杂, MonadPlus有两套潜在的法律 。 普遍认为, MonadPlus应该与mplusmempty形成一个monoid,并且它应该满足左零法则, mempty >>= f = mempty 。 但是,一些MonadPlus ses满足左分配mplus ab >>= f = mplus (a >>= f) (b >>= f) ; 其他人满足左catchmplus (return a) b = return a 。 (请注意, MonadPlus零点/分布类似于Alternative右分配/吸收; (<*>) (=<<)(>>=)更类似于(=<<) (>>=) 。)左分布可能是“更好”任何满足左侧捕获的MonadPlus实例,比如Maybe ,都是一种Alternative但不是第一种MonadPlus 。 而且,由于左边的catch依赖于sorting,所以你可以想象一个新的types包装MaybeAlternative正确的,而不是左 –偏向: a <|> Just b = Just b 。 这样既不会左派也不会左派,但是会是一个完美的Alternative

然而,因为MonadPlus任何types应该使其实例与其Alternative实例相一致(我相信这是需要的,即要求ap(<*>)对于ApplicativeMonad是相等的),你可以想象将MonadPlus类定义为

 class (Monad m, Alternative m) => MonadPlus' m 

class级不需要申报新function; 这只是关于给定types的empty(<|>)服从的一个承诺。 这种devise技术并没有在Haskell标准库中使用,而是用于一些更具math意义的软件包以达到类似的目的; 例如晶格包就是用它来表示一个晶格只是一个连接的半格和满足相同types的半格的思想,这些半 格是用吸收定律连接起来的。

即使你想保证AlternativeMonoid总是一致,你也不能为Alternative做同样的事情,这是因为那种不匹配。 所需的类声明将具有该forms

 class (Applicative f, forall a. Monoid (fa)) => Alternative''' f 

但是(如上所述)甚至GHC Haskell都不支持量化约束。

另外,请注意,如果将Alternative作为MonadPlus的超类,则会要求Applicative作为Monad的超类,所以祝您好运。 如果遇到这个问题,总是WrappedMonad ,它会以明显的方式将任何Monad变成Applicative ; 有一个instance MonadPlus m => Alternative (WrappedMonad m) where ...这正是你所期望的。

 import Data.Monoid import Control.Applicative 

我们来看一个Monoid和Alternative如何与Maybe函子和ZipList函数进行交互的ZipList ,但让我们从头开始,部分是为了让所有的定义在我们的头脑中都是新鲜的,部分是为了停止从tab切换到hackage的所有时间,但主要是我可以运行这个过去的ghci来纠正我的错误!

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

这是Maybe克隆:

 data Perhaps a = Yes a | No deriving (Eq, Show) instance Functor Perhaps where fmap f (Yes a) = Yes (fa) fmap f No = No instance Applicative Perhaps where pure a = Yes a No <*> _ = No _ <*> No = No Yes f <*> Yes x = Yes (fx) 

现在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 

结构1:结合元素:幺半群

也许克隆

首先让我们看看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 

串联固有地在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 

这个monoid结构Perhaps试图在a层面上尽可能多地工作。 注意Monoid a约束,告诉我们我们正在使用a关卡的结构。 这不是一个替代结构,它是派生(解除)Monoid结构。

 instance Monoid a => Monoid (Perhaps a) where mappend = (<++>) mempty = No 

在这里,我使用了数据结构来添加结构。 如果我把Set s组合起来,我可以添加一个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 ? 

但是我们应该用? 。 我说这里唯一明智的select是++ 。 实际上,对于列表, (<>) = (++)

  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 

但是,我们可以用? 我说我们要组合元素,所以我们应该再次使用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 

这是使用拉链合并元素的唯一明智的方式 – 所以它是唯一明智的monoid实例。

有趣的是,这对上面的Maybe的例子不起作用,因为Haskell不知道如何结合Int – 它应该使用+还是* ? 要获得数值数据的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] 

关键

请注意 ,Monoid中的types具有kind *的事实正是让我们把Monoid a放在这里的Monoid a上下文 – 我们也可以添加Eq aOrd a 。 在Monoid中,原始元素很重要。 Monoid实例旨在让您操作和合并结构中的数据。

结构2:更高层次的select:另类

select运算符是相似的,但也是不同的。

也许克隆

 (<||>) :: 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 

这里没有连接 – 我们完全没有使用++ – 这个组合完全是在Perhaps层面上工作的,所以让我们把types签名改为

 (<||>) :: 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 

注意没有限制 – 我们没有使用这个层次的结构,只是在这个结构层次上。 这是一个替代结构。

 instance Alternative Perhaps where (<|>) = (<||>) empty = No 

ZipList克隆

我们应该如何select两个ziplists?

 Zip [1,3,4] <|> Zip [10,20,30,40] = ???? 

在元素上使用<|>会很诱人,但是我们不能,因为元素的types对我们来说是不可用的。 我们先从empty开始。 它不能使用一个元素,因为在定义一个Alternative时我们不知道元素的types,所以它必须是Zip [] 。 我们需要它是<|>的左(而且最好是右)身份,所以

 Zip [] <|> Zip ys = Zip ys Zip xs <|> Zip [] = Zip xs 

Zip [1,3,4] <|> Zip [10,20,30,40]有两个明智的selectZip [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) ,这两个列表可能是无限的,所以比较它们的长度可能永远不会终止,所以它必须select第一个。 因此唯一明智的select是:

 instance Alternative Zip where empty = Zip [] Zip [] <|> x = x Zip xs <|> _ = Zip xs 

这是我们可以定义的唯一明智的select。 注意与Monoid实例有什么不同,因为我们不能混淆元素,我们甚至看不到它们。

关键

请注意 ,因为Alternative需要types的构造函数* -> *所以没有办法添加Ord aEq aMonoid a上下文。 一个select是不允许使用有关结构内的数据的任何信息。 你不能,无论你想做多less,对数据任何事情,除非可能把它扔掉。

要点:Alternative和Monoid有什么区别?

不是很多 – 他们都是单身人士,但总结了最后两节:

Monoid *实例可以组合内部数据。 Alternative (* -> *)实例使它不可能。 Monoid提供灵活性,Alternative提供保证。 种类*(* -> *)是造成这种差异的主要原因。 让他们都可以使用这两种操作。

这是对的,我们的两种口味都是合适的。 Maybe Perhaps String的Monoid实例表示将所有字符放在一起,Alternative实例表示string之间的select。

Maybe的Monoid实例没有什么问题 – 它正在完成它的工作, 结合数据。
Maybe的替代实例没有什么问题 – 它正在做它的工作,在事情之间做出select

Zip的Monoid实例组合了它的元素。 Zip的替代实例被迫select其中一个列表 – 第一个非空列表。

能够做到这一点都很好。

什么是应用上下文的任何用途?

select和应用之间有一些相互作用。 请参阅安泰律师事务所在他的问题中或在他的答案中指出的法律 。

从实际的angular度来看,这是非常有用的,因为Alternative是一些用于一些Applicative Functor的select。 function被用于Applicatives,所以一般的接口类被发明。 Applicative Functors适合表示产生值的计算(IO,Parser,Input UI元素,…),其中一些必须处理失败 – 需要select。

为什么selectempty

why does Alternative need an empty method/member? I may be wrong, but it seems to not be used at all … at least in the code I could find. And it seems not to fit with the theme of the class — if I have two things, and need to pick one, what do I need an 'empty' for?

That's like asking why addition needs a 0 – if you want to add stuff, what's the point in having something that doesn't add anything? The answer is that 0 is the crucual pivotal number around which everything revolves in addition, just like 1 is crucial for multiplication, [] is crucial for lists (and y=e^x is crucial for calculus). In practical terms, you use these do-nothing elements to start your building:

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

Can't we replace MonadPlus with Monad+Alternative?

what's the point of the MonadPlus type class? Can't I unlock all of its goodness by just using something as both a Monad and Alternative? Why not just ditch it? (I'm sure I'm wrong, but I don't have any counterexamples)

You're not wrong, there aren't any counterexamples!

Your interesting question has got Antal SZ, Petr Pudlák and I delved into what the relationship between MonadPlus and Applicative really is. The answer, here and here is that anything that's a MonadPlus (in the left distribution sense – follow links for details) is also an Alternative , but not the other way around.

This means that if you make an instance of Monad and MonadPlus, it satisfies the conditions for Applicative and Alternative anyway . This means if you follow the rules for MonadPlus (with left dist), you may as well have made your Monad an Applicative and used Alternative.

If we remove the MonadPlus class, though, we remove a sensible place for the rules to be documented, and you lose the ability to specify that something's Alternative without being MonadPlus (which technically we ought to have done for Maybe). These are theoretical reasons. The practical reason is that it would break existing code. (Which is also why neither Applicative nor Functor are superclasses of Monad.)

Aren't Alternative and Monoid the same? Aren't Alternative and Monoid completely different?

the 'pedia says that "the Alternative type class is for Applicative functors which also have a monoid structure." I don't get this — doesn't Alternative mean something totally different from Monoid? ie I understood the point of the Alternative type class as picking between two things, whereas I understood Monoids as being about combining things.

Monoid and Alternative are two ways of getting one object from two in a sensible way. Maths doesn't care whether you're choosing, combining, mixing or blowing up your data, which is why Alternative was referred to as a Monoid for Applicative. You seem to be at home with that concept now, but you now say

for types that have both an Alternative and a Monoid instance, the instances are intended to be the same

I disagree with this, and I think my Maybe and ZipList examples are carefully explained as to why they're different. If anything, I think it should be rare that they're the same. I can only think of one example, plain lists, where this is appropriate. That's because lists are a fundamental example of a monoid with ++ , but also lists are used in some contexts as an indeterminate choice of elements, so <|> should also be ++ .

概要

  • We need to define (instances that provide the same operations as) Monoid instances for some applicative functors, that genuinely combine at the applicative functor level, and not just lifting lower level monoids. The example error below from litvar = liftA2 mappend literal variable shows that <|> cannot in general be defined as liftA2 mappend ; <|> works in this case by combining parsers, not their data.

  • If we used Monoid directly, we'd need language extensions to define the instances. Alternative is higher kinded so you can make these instances without requiring language extensions.

Example: Parsers

Let's imagine we're parsing some declarations, so we import everything we're going to need

 import Text.Parsec import Text.Parsec.String import Control.Applicative ((<$>),(<*>),liftA2,empty) import Data.Monoid import Data.Char 

and think about how we'll parse a type. We choose simplistic:

 data Type = Literal String | Variable String deriving Show examples = [Literal "Int",Variable "a"] 

Now let's write a parser for literal types:

 literal :: Parser Type literal = fmap Literal $ (:) <$> upper <*> many alphaNum 

Meaning: parse an upper case character, then many alphaNum eric characters, combine the results into a single String with the pure function (:) . Afterwards, apply the pure function Literal to turn those String s into Type s. We'll parse variable types exactly the same way, except for starting with a lower case letter:

 variable :: Parser Type variable = fmap Variable $ (:) <$> lower <*> many alphaNum 

That's great, and parseTest literal "Bool" == Literal "Bool" exactly as we'd hoped.

Question 3a: If it's to combine applicative's effects with Monoid's behavior, why not just liftA2 mappend

Edit:Oops – forgot to actually use <|> !
Now let's combine these two parsers using Alternative:

 types :: Parser Type types = literal <|> variable 

This can parse any Type: parseTest types "Int" == Literal "Bool" and parseTest types "a" == Variable "a" . This combines the two parsers , not the two values . That's the sense in which it works at the Applicative Functor level rather than the data level.

However, if we try:

 litvar = liftA2 mappend literal variable 

that would be asking the compiler to combine the two values that they generate, at the data level. 我们得到

 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 

So we found out the first thing; the Alternative class does something genuinely different to liftA2 mappend , becuase it combines objects at a different level – it combines the parsers, not the parsed data. If you like to think of it this way, it's combination at the genuinely higher-kind level, not merely a lift. I don't like saying it that way, because Parser Type has kind * , but it is true to say we're combining the Parser s, not the Type s.

(Even for types with a Monoid instance, liftA2 mappend won't give you the same parser as <|> . If you try it on Parser String you'll get liftA2 mappend which parses one after the other then concatenates, versus <|> which will try the first parser and default to the second if it failed.)

Question 3b: In what way does Alternative's <|> :: fa -> fa -> fa differ from Monoid's mappend :: b -> b -> b ?

Firstly, you're right to note that it doesn't provide new functionality over a Monoid instance.

Secondly, however, there's an issue with using Monoid directly: Let's try to use mappend on parsers, at the same time as showing it's the same structure as Alternative :

 instance Monoid (Parser a) where mempty = empty mappend = (<|>) 

哎呀! 我们得到

 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)' 

So if you have an applicative functor f , the Alternative instance shows that fa is a monoid, but you could only declare that as a Monoid with a language extension.

Once we add {-# LANGUAGE TypeSynonymInstances #-} at the top of the file, we're fine and can define

 typeParser = literal `mappend` variable 

and to our delight, it works: parseTest typeParser "Yes" == Literal "Yes" and parseTest typeParser "a" == Literal "a" .

Even if you don't have any synonyms ( Parser and String are synonyms, so they're out), you'll still need {-# LANGUAGE FlexibleInstances #-} to define an instance like this one:

 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) 

(The monoid instance for Maybe gets around this by lifting the underlying monoid.)

Making a standard library unnecessarily dependent on language extensions is clearly undesirable.


所以你有它。 Alternative is just Monoid for Applicative Functors (and isn't just a lift of a Monoid). It needs the higher-kinded type fa -> fa -> fa so you can define one without language extensions.

Your other Questions, for completeness:

  1. Why does Alternative need an empty method/member?
    Because having an identity for an operation is sometimes useful. For example, you can define anyA = foldr (<|>) empty without using tedious edge cases.

  2. what's the point of the MonadPlus type class? Can't I unlock all of its goodness by just using something as both a Monad and Alternative? No. I refer you back to the question you linked to :

Moreover, even if Applicative was a superclass of Monad, you'd wind up needing the MonadPlus class anyways, because obeying empty <*> m = empty isn't strictly enough to prove that empty >>= f = empty .

….and I've come up with an example: Maybe. I explain in detail, with proof in this answer to Antal's question. For the purposes of this answer, it's worth noting that I was able to use >>= to make the MonadPlus instance that broke the Alternative laws.


Monoid structure is useful. Alternative is the best way of providing it for Applicative Functors.

I won't cover MonadPlus because there is disagreement about its laws.


After trying and failing to find any meaningful examples in which the structure of an Applicative leads naturally to an Alternative instance that disagrees with its Monoid instance*, I finally came up with this:

Alternative's laws are more strict than Monoid's, because the result cannot depend on the inner type. This excludes a large number of Monoid instances from being Alternatives. These datatypes allow partial (meaning that they only work for some inner types) Monoid instances which are forbidden by the extra 'structure' of the * -> * kind. 例子:

  • the standard Maybe instance for Monoid assumes that the inner type is Monoid => not an Alternative

  • ZipLists, tuples, and functions can all be made Monoids, if their inner types are Monoids => not Alternatives

  • sequences that have at least one element — cannot be Alternatives because there's no empty :

     data Seq a = End a | Cons a (Seq a) deriving (Show, Eq, Ord) 

On the other hand, some data types cannot be made Alternatives because they're * -kinded:

  • unit — ()
  • Ordering
  • numbers, booleans

My inferred conclusion: for types that have both an Alternative and a Monoid instance, the instances are intended to be the same. 另请参阅此答案 。


excluding Maybe, which I argue doesn't count because its standard instance should not require Monoid for the inner type, in which case it would be identical to Alternative

I understood the point of the Alternative type class as picking between two things, whereas I understood Monoids as being about combining things.

If you think about this for a moment, they are the same.

The + combines things (usually numbers), and it's type signature is Int -> Int -> Int (or whatever).

The <|> operator selects between alternatives, and it's type signature is also the same: take two matching things and return a combined thing.