[英]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
,并且y
和z
也只能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 fa
和mappend:: 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 = ...
xs
和ys
有哪些类型? 好吧, Ap xs:: Ap fa
和Ap:: 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 a
的EqProp
的定义中,您基本上是在说只检查列表的前 3000 个元素是否相等是可以的。 因此,即使遇到无限列表,只要前 3000 个元素相等,我们就可以仅向前 go 并假设列表相等。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.