我是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
吗?感谢。
答案 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 : []