`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]
我是 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]