我一直在浏览Typeclassopedia以学习类型类。我理解Alternative
(以及MonadPlus
就此问题了。)
我遇到的问题:
'pedia说“替代类型类适用于具有幺半群结构的Applicative仿函数。”我不明白 - 不是替代意味着与Monoid完全不同的东西吗?也就是说,我理解Alternative类的重点在两件事之间拣选,而我认为Monoids是关于组合事物的。
为什么备选方案需要empty
方法/成员?我可能错了,但似乎根本没有使用......至少在我能找到的code中。它似乎不符合课程的主题 - 如果我有两件事,需要选一件,我需要什么'空'?
为什么Alternative类需要一个Applicative约束,为什么它需要一种* -> *
?为什么不只有<|> :: a -> a -> a
?所有的实例仍然可以用同样的方式实现......我想(不确定)。 Monoid没有提供什么价值?
MonadPlus
类型的重点是什么?我只能使用Monad
和Alternative
这样的东西来解锁它的全部优点吗?为什么不放弃呢? (我确定我错了,但我没有任何反例)
希望所有这些问题都是连贯的......!
Bounty更新:@Antal的答案是一个很好的开始,但Q3仍然是开放的:替代品提供的Monoid没有?我发现this answer不能令人满意,因为它缺乏具体的例子,并且特别讨论了Alternative的高知名度如何将它与Monoid区分开来。
如果将应用效果与Monoid的行为结合起来,为什么不呢:
liftA2 mappend
这对我来说更加令人困惑,因为许多Monoid实例与Alternative实例完全相同。
这就是为什么我正在寻找具体的例子,它们展示了为什么备选是必要的,以及它与Monoid的不同之处 - 或者意味着什么不同。
答案 0 :(得分:8)
我们需要定义(提供相同操作的实例)一些应用仿函数的Monoid实例,它们在应用仿函数级别真正组合,而不仅仅是提升低级别的monoid。 litvar = liftA2 mappend literal variable
下面的示例错误表明,<|>
通常不能定义为liftA2 mappend
; <|>
在这种情况下通过组合解析器而不是它们的数据来工作。
如果我们直接使用Monoid,我们需要语言扩展来定义实例。 Alternative
处于较高的状态,因此您可以在不需要语言扩展的情况下创建这些实例。
让我们假设我们正在解析一些声明,因此我们导入了我们需要的所有内容
import Text.Parsec
import Text.Parsec.String
import Control.Applicative ((<$>),(<*>),liftA2,empty)
import Data.Monoid
import Data.Char
并考虑我们将如何解析一个类型。我们选择简单化:
data Type = Literal String | Variable String deriving Show
examples = [Literal "Int",Variable "a"]
现在让我们为文字类型编写一个解析器:
literal :: Parser Type
literal = fmap Literal $ (:) <$> upper <*> many alphaNum
含义:解析upper
个案字符,然后many alphaNum
个字符,将结果合并为一个带有纯函数(:)
的字符串。然后,应用纯函数Literal
将String
转换为Type
s。我们将以完全相同的方式解析变量类型,除了以lower
大小写字母开头:
variable :: Parser Type
variable = fmap Variable $ (:) <$> lower <*> many alphaNum
这很棒,parseTest literal "Bool" == Literal "Bool"
正如我们所希望的那样。
liftA2 mappend
编辑:哎呀 - 忘了实际使用<|>
!
现在让我们使用Alternative来结合这两个解析器:
types :: Parser Type
types = literal <|> variable
这可以解析任何类型:parseTest types "Int" == Literal "Bool"
和parseTest types "a" == Variable "a"
。
这结合了两个解析器,而不是两个值。这就是它在Applicative Functor级别而不是数据级别上工作的意义。
但是,如果我们尝试:
litvar = liftA2 mappend literal variable
这将要求编译器在数据级别组合它们生成的两个值。 我们得到
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
所以我们发现了第一件事; Alternative类与liftA2 mappend
做了一些真正不同的事情,因为它组合了不同级别的对象 - 它结合了解析器,而不是解析数据。如果你想以这种方式思考它,那就是真正更高级别的组合,而不仅仅是电梯。我不喜欢这样说,因为Parser Type
有*
种类,但我们说的是Parser
s而不是Type
s的组合是真的。 。
(即使对于具有Monoid实例的类型,liftA2 mappend
也不会为您提供与<|>
相同的解析器。如果您在Parser String
上尝试,则会获得liftA2 mappend
其中一个接一个地解析然后连接,而<|>
将尝试第一个解析器,如果失败则默认为第二个解析器。)
<|> :: f a -> f a -> f a
与Monoid的mappend :: b -> b -> b
有什么不同?首先,您应该注意到它没有提供Monoid实例的新功能。
其次,直接使用Monoid存在问题:
让我们尝试在解析器上使用mappend
,同时显示它与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)'
因此,如果您有一个应用仿函数f
,则Alternative
实例会显示f a
是一个幺半群,但您只能将其声明为带有语言的Monoid
扩展
一旦我们在文件顶部添加{-# LANGUAGE TypeSynonymInstances #-}
,我们就可以了,并且可以定义
typeParser = literal `mappend` variable
令我们高兴的是,它有效: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)
(可能的monoid实例通过提升底层的monoid来解决这个问题。)
使标准库不必要地依赖于语言扩展显然是不可取的。
所以你有它。替代方案只是Monoid for Applicative Functors(并不仅仅是Monoid的提升)。它需要更高级别的f a -> f a -> f a
类型,因此您可以定义一个没有语言扩展名的类型。
为什么Alternative需要一个空方法/成员?
因为拥有操作的身份有时是有用的。
例如,您可以在不使用繁琐的边缘情况的情况下定义anyA = foldr (<|>) empty
。
MonadPlus类型的重点是什么?我不能通过使用Monad和Alternative等东西来解锁所有的善良吗? 不,我推荐您回到question you linked to:
此外,即使Applicative是Monad的超类,你仍然需要MonadPlus类,因为服从
empty <*> m = empty
并不足以证明empty >>= f = empty
。
....我想出了一个例子:也许吧。我详细解释了this answer中对安塔尔问题的证明。出于这个答案的目的,值得注意的是我能够使用&gt;&gt; =使MonadPlus实例破坏了替代法则。
幺半群结构很有用。替代方案是为Applicative Functors提供它的最佳方式。
答案 1 :(得分:4)
我不会介绍MonadPlus,因为对其法律存在分歧。
在尝试并且未能找到任何有意义的示例,其中Applicative的结构自然地导致不同意其Monoid实例*的Alternative实例时,我终于提出了这个:
替代法则比Monoid更严格,因为的结果不能依赖于内部类型。这将大量Monoid实例排除在替代品之外。
这些数据类型允许部分(意味着它们仅适用于某些内部类型)Monoid实例被* -> *
类型的额外“结构”禁止。例子:
Monoid的标准Maybe实例假设内部类型为Monoid =&gt;不是替代
ZipLists,元组和函数都可以制作Monoids,如果他们的内部类型是Monoids =&gt;不是替代品
具有至少一个元素的序列 - 不能是替代品,因为没有empty
:
data Seq a
= End a
| Cons a (Seq a)
deriving (Show, Eq, Ord)
另一方面,某些数据类型无法替代,因为它们是*
- kinded:
()
Ordering
我推断的结论:对于同时具有Alternative和Monoid实例的类型,实例应该是相同的。另请参阅this answer。
不包括Maybe,我认为这不算数,因为它的标准实例不应该要求Monoid作为内部类型,在这种情况下它将与Alternative 相同
答案 2 :(得分:2)
我理解Alternative类的意思是在两件事之间挑选,而我理解Monoids是关于结合事物。
如果你想一下这一点,它们是一样的。
+
组合事物(通常是数字),它的类型签名是Int -> Int -> Int
(或其他)。
<|>
运算符在备选项之间进行选择,并且它的类型签名也是相同的:获取两个匹配的东西并返回组合的东西。