代码之家  ›  专栏  ›  技术社区  ›  Yola

“sequenceA”如何工作

  •  10
  • Yola  · 技术社区  · 8 年前

    我对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 回复  |  直到 8 年前
        1
  •  9
  •   Will Ness Derri Leahy    7 年前

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

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

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

    现在,当你给sequenceA一个函数列表时 (Num a) => [a -> a] 列表将是 Traversable ,列表中的内容应该是 适用性 因此,它使用函数的应用实例。

    所以当你把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) = \x -> [(+1) x] .

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

        2
  •  3
  •   d8d0d65b3f7cf42    8 年前

    它正在使用 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上。

        3
  •  3
  •   Paul Parker    7 年前

    对于任何难以接受这一论点的人 sequenceA [(+1)] 神奇地应用于两者 (+1) const [] ,这是给你的。这是我意识到这一点后唯一的症结所在 pure [] = const [] .

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

    使用lambdas(这样当我们开始将函数应用程序视为函子和应用程序时,我们可以显式地显示和移动内容):

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

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

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

    影响 <$> 就是拔 (+1) 从包装中取出 ((->) r) ,或 \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-> . 我保留着 在里面提醒我们 的将在稍后应用,因此这里的确有点假y。函数应用程序将在很短的时间内重新连接 。这两个函数依赖于相同的 ,正是因为它们具有相同的函数应用程序包装,即应用程序。没有他们的 \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
    

    看见这很有道理 序列A[(+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]