`sequenceA`的工作原理

时间:2016-01-06 09:01:53

标签: haskell applicative

我是Haskell的新手,并试图了解这是如何工作的?

sequenceA [(+3),(+2),(+1)] 3

我已从定义

开始
sequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA [] = pure []  
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs

然后展开递归到这个

(:) <$> (+3) <*> $ (:) <$> (+2) <*> $ (:) <$> (+1) <*> pure [] 
(:) <$> (+3) <*> $ (:) <$> (+2) <*> $ (:) <$> (+1) <*> []

但是我不明白将为<*>((->) r)

调用哪个应用仿函数运算符[]
(:) <$> (+1) <*> []

有人可以一步一步地逐步解析sequenceA [(+3),(+2),(+1)] 3吗?感谢。

3 个答案:

答案 0 :(得分:7)

这可以从序列A的类型看出:

sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)

参数的外部类型必须是Traverable,其内部类型必须是Applicative

现在,当您为sequenceA提供函数列表(Num a) => [a -> a]时,列表将是Traversable,列表中的内容应为Applicative。因此,它使用适用于函数的实例。

因此,当您将sequenceA应用于[(+3),(+2),(+1)]时,将构建以下计算:

sequenceA [(+3),(+2),(+1)] = (:) <$> (+3) <*> sequenceA [(+2),(+1)]
sequenceA [(+2),(+1)]      = (:) <$> (+2) <*> sequenceA [(+1)]
sequenceA [(+1)]           = (:) <$> (+1) <*> sequenceA []
sequenceA []               = pure []

让我们看看最后一行。 pure []获取一个空列表并将其放入一些应用程序结构中。正如我们刚才看到的,这种情况下的适用结构是((->) r)。因此,sequenceA [] = pure [] = const []

现在,第3行可以写成:

sequenceA [(+1)] = (:) <$> (+1) <*> const []

将此功能与<$><*>结合使用会产生并行应用。 (+1)const []都应用于同一参数,并使用(:)

组合结果

因此,sequenceA [(+1)]会返回一个函数,该函数采用Num a => a类型值,对其应用(+1),然后将结果预先添加到空列表中,\x -> (:) ((1+) x) (const [] x) = {{ 1}}。

这个概念可以进一步扩展到\x -> [(+1) x]。它产生一个函数,它接受一个参数,将所有三个函数应用于该参数,并将三个结果与sequenceA [(+3), (+2), (+1)]结合起来收集在列表中:(:)

答案 1 :(得分:2)

它正在使用instance Applicative ((->) a)

在ghci中试试这个:

Prelude> :t [(+3),(+2),(+1)]
[(+3),(+2),(+1)] :: Num a => [a -> a]

Prelude> :t sequenceA
sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)

和模式匹配参数类型:t = [], f = (->) a 和Applicative约束在f。

答案 2 :(得分:1)

对于那些无法接受sequenceA [(+1)]的论证只是神奇地适用于(+1)const []的人,这适合您。在意识到pure [] = const []之后,这是我唯一的关键点。

sequenceA [(+1)] = (:) <$> (+1) <*> const []

使用lambdas(所以当我们开始处理函数应用程序时,我们可以明确地显示和移动事物,如仿函数和应用程序):

sequenceA [(+1)] = \b c -> ( (:) b c ) <$> ( \a -> (+1) a ) <*> ( \a -> const [] a )

(<$>)(<*>)都是infixl 4.这意味着我们从左到右阅读和评估,即我们从(<$>)开始。

(<$>) :: Functor f => (a -> b) -> f a -> f b

<$>的效果是从我们的lambda代码中提取(+1)的包装((->) r)或OR \a ->,并将其应用于{{ 1}}它取代\b c -> ( (:) b c ),然后重新应用包装器(在下面一行等号后面显示的b):

\a

请注意,sequenceA [(+1)] = \a c -> ( (:) ((+1) a) c ) <*> ( \a -> const [] a ) 仍在等待参数(:),而c仍在等待(+1)。现在,我们来到应用部分。

请记住:a。我们的(<*>) :: f (a -> b) -> f a -> f b这里是函数应用程序f

双方现在拥有相同的包装,即\a ->我将\a ->保留在那里,以提醒我们以后会在a被应用的位置,所以它确实变得有点伪 - 在这里函数应用程序将以简单的方式连接备份。两个函数都依赖于相同的a,正是因为它们具有相同的函数应用程序包装器,即应用程序。没有他们的a包装器(感谢\a ->),它就是这样的:

<*>

现在,( \c -> ( (:) ((+1) a) c ) ) (const [] a) = ( (:) ((+1) a) (const [] a) ) -- Ignore those a's, they're placeholders. 做的最后一件事就是将此结果弹回到它的包装<*>中:

\a ->

稍微加油一下:

sequenceA [(+1)] = \a -> ( (:) ((+1) a) (const [] a) )

请参阅! sequenceA [(+1)] = \a -> (+1) a : const [] a 的论证同时适用于sequenceA [(+1)](+1),这是完全合理的。例如,应用2可以得到:

const

请记住sequenceA [(+1)] 2 = (+1) 2 : const [] 2 ,因此只是忽略了它的输入:

const a b :: a -> b -> a

或者,更甜蜜地说:

sequenceA [(+1)] 2 = 3 : []