`sequenceA` 是如何工作的

How `sequenceA` works

我是 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吗?谢谢。

正在使用 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 应用约束在 f.

这个从sequenceA的类型可以看出:

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)] returns 一个接受 Num a => a 类型值的函数,对其应用 (+1),然后将结果添加到一个空列表中,\x -> (:) ((1+) x) (const [] x) = \x -> [(+1) x].

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

对于无法接受 sequenceA [(+1)] 的论点神奇地适用于 (+1)const [] 的人来说,这是给你的。在意识到 pure [] = const [].

之后,这是我唯一的症结所在
sequenceA [(+1)] = (:) <$> (+1) <*> const []

使用 lambda(因此当我们开始像仿函数和应用程序一样处理函数应用时,我们可以明确地显示和移动事物):

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

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

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

<$> 的作用是从它的包装器 ((->) r) 中提取 (+1),或者从我们的 lambda 代码中提取 \a ->,并将其应用于 \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 会应用到什么地方,所以这里确实有点假。功能应用程序将在 jiffy 内恢复连接。这两个函数都依赖于相同的 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)const,这是完全合理的。例如,应用 2 会得到:

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

记住 const a b :: a -> b -> a,因此忽略它的输入:

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

或者,更贴心:

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