繁体   English   中英

Haskell quickBatch:Ap applicative monoid

[英]Haskell quickBatch: Ap applicative monoid

根据ZipList Monoid haskell提供的建议,我创建了这个有效的代码:

newtype Ap f a = Ap { getAp :: f a }
  deriving (Eq, Show)
instance (Applicative f, Semigroup a) =>
  Semigroup (Ap f a) where
    Ap xs <> Ap ys = 
      Ap $ liftA2 (<>) xs ys
instance (Applicative f, Monoid a) => 
  Monoid (Ap f a) where
    mempty = Ap $ pure mempty
    Ap xs `mappend` Ap ys = 
      Ap $ liftA2 mappend xs ys
app :: Ap ZipList (Sum Int)
app = Ap (ZipList [1,2 :: Sum Int])
instance Arbitrary (f a) =>
  Arbitrary (Ap f a) where
    arbitrary = Ap <$> arbitrary 
instance Eq a => EqProp (Ap ZipList a) where
  xs =-= ys = xs' `eq` ys' where 
    xs' = 
      let (Ap (ZipList l)) = xs
        in take 3000 l
    ys' = 
      let l = (getZipList . getAp) ys
        in take 3000 l
main :: IO ()
main = do
  quickBatch $ monoid app

但是,我并不完全理解代码是如何工作的。 为什么它是mempty = Ap $ pure mempty 这个方程是如何计算或推导出来的? 为什么是Ap xs 'mappend' Ap ys 我本来以为既然是Monoid (Ap fa) ,它应该是Ap f xs 'mappend' Ap f ys

为什么在 Ap 上运行 quickBatch monoid 测试时,它不会导致 mconcat 测试中的堆栈溢出,如Haskell quickBatch 所示:在 mconcat 测试 ZipList Monoid 导致堆栈溢出

它实际上是唯一可能的定义(⊥ 除外)。

  • 对幺半群一无所知,您可以编写的唯一表达式是mempty <> mempty <>... <> mempty ,根据幺半群定律,这只是mempty
  • 对应用程序一无所知,只有您可以编写的表达式的形式f <$> pure y <*> pure z <*>... ,这与pure $ fy z...相同,其中f是一些幺半群上的 n 元 function - 再次只能具有\y' z'... -> y' <> mempty <>... <> z' <> mempty ,并且yz也只能mempty

所以,无论你能写什么表达式,只要你输入的 monoid 和 applicative 是合规的,它总是与pure mempty相同。

为什么它是mempty = Ap $ pure mempty 这个方程是如何计算或推导出来的? 为什么是Ap xs 'mappend' Ap ys 我本来以为既然是Monoid (Ap fa) ,它应该是Ap f xs 'mappend' Ap f ys

为了回答这些问题,仔细看看Ap本身的定义是很重要的:

newtype Ap f a = Ap { getAp :: f a }

这个声明引入了一个新的类型Ap和一个新的构造函数Ap它们具有相同的名称,但它们绝对是不同的实体。

Ap类型有 kind (Type -> Type) -> Type -> Type ,也就是说它需要两个 arguments: f ,它本身是一个 function 类型,和a ,它只是一个类型。 我们可以在类型签名中使用Ap类型,当我们编写app:: Ap ZipList (Sum Int)或 class 实例时,如instance Eq a => EqProp (Ap ZipList a)

构造函数Ap的类型为fa -> Ap fa (注意此处使用的是Ap的类型版本。)。 此构造函数接受一个参数,即fa类型的值,以生成Ap fa类型的值。 因此,例如,您可以编写:

t1 :: Ap Maybe Int
t1 = Ap (Just 3)

t2 :: Ap [] Bool
t2 = Ap [True, False]

t3 :: Ap ZipList Int
t3 = Ap (ZipList [1,2,3])

请注意,在每种情况下,类型Ap采用两个 arguments,但构造函数Ap采用一个参数。

现在,让我们考虑如何为Ap fa编写Monoid实例。 让我们回顾一下Monoid class:

class Semigroup a => Monoid a where
  mempty  :: a
  mappend :: a -> a -> a

因此, instance (Applicative f, Monoid a) => Monoid (Ap fa) ,我们需要mempty:: Ap famappend:: Ap fa -> Ap fa -> Ap fa 我们怎么写mempty 好吧,首先,我们需要一个Ap fa类型的值,到目前为止,我们看到的唯一方法是使用Ap构造函数。 回想一下Ap构造函数,这意味着我们需要一个fa类型的值,我们可以将其提供给它。 我们如何生产其中之一? 幸运的是,我们知道Monoid a ,所以我们可以访问mempty:: a ,并且我们知道Applicative f ,所以我们可以访问pure:: forall x. x -> fx pure:: forall x. x -> fx 将这两者结合在一起,我们可以创建一个pure mempty:: fa 剩下的就是将其提供给Ap构造函数:

  mempty = Ap $ pure mempty

接下来,我们需要定义mappend 我们得到了两个Ap fa类型的值,这意味着对于某些值x ,它们必须是Ap x的形式(请记住,此处Ap Ap x中的 Ap 是构造函数,而不是类型)。 所以,我们从模式匹配开始:

  Ap xs `mappend` Ap ys = ...

xsys有哪些类型? 好吧, Ap xs:: Ap faAp:: fa -> Ap fa ,所以xs, ys:: fa 我们需要以某种方式将这两个fa fa的值,然后我们可以使用Ap构造函数将其包装为 output。 我们可以使用liftA2 mappend xs ys来做到这一点。 这给了我们:

  Ap xs `mappend` Ap ys = Ap $ liftA2 mappend xs ys

作为此处的注释,请查看编写以下内容的意义:

  Ap f xs `mappend` Ap g ys` = -- pattern error!

因为我们会混淆类型Ap ,它需要两个 arguments 和构造函数Ap ,它只需要一个。


以及为什么在Ap上运行quickBatch monoid测试时,在mconcat测试时不会导致stackoverflow

堆栈溢出是尝试比较两个无限列表是否相等的结果。 GHC 将继续检查每个元素以查找列表的末尾或两个不相等的元素,并且由于列表无限长,因此程序不会终止(或将耗尽内存)。

但是,在您对Ap ZipList aEqProp的定义中,您基本上是在说只检查列表的前 3000 个元素是否相等是可以的。 因此,即使遇到无限列表,只要前 3000 个元素相等,我们就可以仅向前 go 并假设列表相等。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM