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

时间:2012-10-26 04:11:15

标签: haskell typeclass

我一直在浏览Typeclassopedia以学习类型类。我理解Alternative(以及MonadPlus就此问题了。)

我遇到的问题:

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

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

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

  • MonadPlus类型的重点是什么?我只能使用MonadAlternative这样的东西来解锁它的全部优点吗?为什么不放弃呢? (我确定我错了,但我没有任何反例)

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


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

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

liftA2 mappend

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

这就是为什么我正在寻找具体的例子,它们展示了为什么备选是必要的,以及它与Monoid的不同之处 - 或者意味着什么不同。

3 个答案:

答案 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个字符,将结果合并为一个带有纯函数(:)的字符串。然后,应用纯函数LiteralString转换为Type s。我们将以完全相同的方式解析变量类型,除了以lower大小写字母开头:

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

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

问题3a:如果要将应用效果与Monoid的行为结合起来,为什么不只是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其中一个接一个地解析然后连接,而<|>将尝试第一个解析器,如果失败则默认为第二个解析器。)

问题3b:Alternative <|> :: 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"

即使您没有任何同义词(ParserString是同义词,因此它们已经出局),您仍然需要{-# 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类型,因此您可以定义一个没有语言扩展名的类型。

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

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

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

  3.   

    此外,即使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:

  • unit - ()
  • Ordering
  • 数字,布尔

我推断的结论:对于同时具有Alternative和Monoid实例的类型,实例应该是相同的。另请参阅this answer


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

答案 2 :(得分:2)

  

我理解Alternative类的意思是在两件事之间挑选,而我理解Monoids是关于结合事物。

如果你想一下这一点,它们是一样的。

+组合事物(通常是数字),它的类型签名是Int -> Int -> Int(或其他)。

<|>运算符在备选项之间进行选择,并且它的类型签名也是相同的:获取两个匹配的东西并返回组合的东西。