很难直接回答您的问题。孤立地考虑,您提出的实例没有根本性的错误。尽管如此,仍有很多东西可以支持现有的Alternative 列表实例。
诚然,它也不适合MonadPlus,但我认为这是为救赎付出的小小代价。
沿着这条路线走的一个问题是,Alternative 旨在捕捉与MonadPlus 相同的一般概念,但就Applicative 而不是Monad 而言。引用a relevant answer by Edward Kmett:
实际上,Alternative 是 Applicative,MonadPlus 是 Monad。
从这个角度来看,Alternative 和 MonadPlus 实例不匹配会造成混淆和误导,就像 Applicative 和 Monad 实例的类似情况一样。
(对此论点的一个可能的反驳是想知道为什么我们仍然需要关心MonadPlus,因为它表达了相同的概念并提供了与Alternative 基本相同的方法。但应该注意的是, MonadPlus 法则比 Alternative 法则更强,因为它的方法与 Monad 的相关交互不能用 Alternative 来表达。既然如此,MonadPlus 仍然具有以下含义它自己的,并且可以想象的类改革的结果将保留它作为一个仅限法律的类,例如在this answer by Antal Spector-Zabusky的最后一节中讨论的那样。)
鉴于这些考虑,在下文中我将假设MonadPlus 的持续相关性。这使得编写此答案的其余部分变得更加容易,因为 MonadPlus 是 Haskell 中一般概念的原始表达,因此在跟踪 Alternative 的列表实例的起源时非常有必要引用它。
在我看来,这将是一个不错的(<|>),比(++) 更好。选择第一个非空列表感觉更像是我对名为 Alternative 的类型类的期望,而不是串联列表。
不过,追查MonadPlus 和Alternative 的根源表明,串联列表实例不仅是完善的,而且是典型的。例如,引用 Hutton 和 Meijer 的经典论文,Monadic parsing in Haskell (1998),p。 4:
也就是说,类型构造函数m是类MonadZero的成员,如果它是类Monad的成员,并且它还配备了指定类型的值zero。以类似的方式,MonadPlus 类通过添加指定类型的 (++) 操作来构建在 MonadZero 类之上。
(请注意,作者确实使用(++) 作为mplus 的名称。)
mplus 在这里捕获的概念是非确定性选择:如果计算 u 和 v 都有一些可能的结果,那么 u `mplus` v 的可能结果将是 @987654375 的所有可能结果@ 和 v。最基本的实现是通过MonadPlus 用于列表,尽管这个想法扩展到涵盖其他非确定性单子,例如 Hutton 和 Meijer 的Parser:
newtype Parser a = Parser (String -> [(a,String)])
换一种说法,我们可以将非确定性选择描述为包容性析取,而您提出的操作是(左偏)排他性选择的一种形式。 (值得注意的是,Hutton 和 Meijer 还为他们的 Parser 定义了一个确定性选择运算符 (+++),这与您的运算符很像,只是它只选择第一个成功计算的第一个结果。)
进一步的相关观察:transformers 中没有 mtl 类对应项的 monad 转换器之一是 ListT。之所以如此,是因为概括ListT 功能的类正是MonadPlus。引用a Gabriella Gonzalez comment:
MonadPlus 基本上是“list monad”类型的类。例如:cons a as = return a `mplus` as 和 nil = mzero。
请注意,transformers'ListT 的损坏不是问题。一般来说,ListT-done-right 的各种表述都配备了一个串联的MonadPlus 实例(例如:one、two、three)。
我们可能希望保留Alternative [] 和MonadPlus [] 实例的原因如此之多。尽管如此,如果它没有认识到 as Will Ness reminds us 存在多种合理的选择概念,并且您的运营商体现了其中之一,那么这个答案将是缺乏的。
Alternative 和MonadPlus 的“官方”法律(即文档中实际提到的法律)没有指定单一的选择概念。既然如此,我们最终会在同一 Alternative/MonadPlus 保护伞下得到非确定性(例如mplus @[])和确定性(例如mplus @Maybe)选择实例。此外,如果有人选择无视我上面的论点并将mplus @[] 替换为您的运营商,那么“官方”法律中的任何内容都不会阻止他们。多年来,有一些关于reforming MonadPlus 的讨论,通过将其划分为具有额外规则的类,以区分不同的选择概念。不过,这种改革实际发生的可能性似乎并不高(大量流失而实际收益相对较小)。
为了对比,有趣的是考虑近乎半透明的解释,这是对 MonadPlus 和 Alternative 的重新想象之一,可能会在假设的类层次结构改革中被调用。有关它的完整描述,请参阅 Rivas、Jaskelioff 和 Schrijvers,A Unified View of Monadic and Applicative Non-determinism (2018)。对于我们目前的目的,只需注意解释通过在幺半群定律中添加Alternative 的“左零”和“左分布”定律来调整类以进行非确定性选择...
empty <*> x = empty
(f <|> g) <*> x = (f <*> x) <|> (g <*> x)
...以及MonadPlus:
mzero >>= k = mzero
(m1 `mplus` m2) >>= k = (m1 >>= k) `mplus` (m2 >>= k)
(那些MonadPlus 的法律比Alternative 的法律严格。)
特别是,您的选择运算符遵循所谓的Alternative 左分布规律,但不是MonadPlus 之一。在这方面,它类似于mplus @Maybe。 MonadPlus 左侧分布使得很难(可能不可能,尽管我现在手头没有证据)放弃任何结果在 mplus,因为我们无法判断,在法律的右侧,如果不检查 m1 和 m2 的结果,m1 >>= k 或 m2 >>= k 是否会失败。为了用有形的东西来结束这个答案,这里是这一点的演示:
-- Your operator.
(<?>) :: [a] -> [a] -> [a]
[] <?> ys = ys
xs <?> _ = xs
filter' :: (a -> Bool) -> [a] -> [a]
filter' p xs = xs >>= \x -> if p x then [x] else []
-- If MonadPlus left distribution holds, then:
-- filter' p (xs `mplus` ys) = filter' p xs `mplus` filter' p ys
GHCi> filter' even ([1,3,5] <|> [0,2,4])
[0,2,4]
GHCi> filter' even [1,3,5] <|> filter' even [0,2,4]
[0,2,4]
GHCi> filter' even ([1,3,5] <?> [0,2,4])
[]
GHCi> filter' even [1,3,5] <?> filter' even [0,2,4]
[0,2,4]