【问题标题】:How to combine two composed applicative functors?如何组合两个组合的应用函子?
【发布时间】:2020-05-28 13:45:40
【问题描述】:

我有两个组合的应用函子 Maybe [Integer] 并想将它们与 <$>/<*> 结合起来,但我坚持应用应用操作。以下内容不进行类型检查:

(<*>) (<*>) ((<$>) ((+) <$>) $ Just [1,2,3]) $ Just [4,5,6]

预期结果:

Just [5,6,7,6,7,8,7,8,9]

函子部分起作用,即作为第一个参数传递给&lt;*&gt;的中间值是Just [Integer -&gt; Integer]。我习惯了 S 表达式,所以我很难使用 Haskell 语法。我知道Compose,但我对没有抽象的纯粹组合感兴趣。

【问题讨论】:

  • 哇,这太混乱了!多层&lt;*&gt;/&lt;$&gt; 只是一团糟。请改用liftA2
  • 或者至少不要尝试使用无点样式。这更清楚:(liftA2 . liftA2) (+) = \a b -&gt; (\a' b' -&gt; (+) &lt;$&gt; a' &lt;*&gt; b') &lt;$&gt; a &lt;*&gt; b
  • 如果你真的希望它是免费的,这里有一个由pointfree.io((&lt;*&gt;) . ((+) &lt;$&gt;)) &lt;$&gt; a &lt;*&gt; b生成的版本
  • @Hjulle:lambda 版本和无点版本都非常适合理解基本原理。谢谢!
  • 我现在已将这些 cmets 扩展为更完整的答案

标签: haskell composition applicative


【解决方案1】:

作为夏立尧said,使用liftA2 可以减少混乱。

但是如果你还想看看它在底层操作方面会变成什么,我们可以扩展liftA2的定义:

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x y = f <$> x <*> y

所以解决方案变成了

(liftA2 . liftA2) (+) (Just [1,2,3]) (Just [4,5,6])
= liftA2 (liftA2 (+)) (Just [1,2,3]) (Just [4,5,6])
= (\f x y -> f <$> x <*> y) ((\f x y -> f <$> x <*> y) (+)) (Just [1,2,3]) (Just [4,5,6])
= ((\f x y -> f <$> x <*> y) (+)) <$> Just [1,2,3] <*> Just [4,5,6]
= (\x y ->  (+) <$> x <*> y) <$> Just [1,2,3] <*> Just [4,5,6]

现在,这不像您上面的示例那样采用无点样式,我真的认为将其转换为无点有帮助,但这是http://pointfree.io 的输出:

((<*>) . ((+) <$>)) <$> Just [1, 2, 3] <*> Just [4, 5, 6]

我们可以通过 eta-expanding 看到这是一样的:

(<*>) . ((+) <$>)
= \x y -> ((<*>) . ((+) <$>)) x y
= \x y -> ((<*>) $ ((+) <$>) x) y
= \x y -> ((<*>) ((+) <$> x)) y
= \x y -> (<*>) ((+) <$> x) y
= \x y -> ((+) <$> x) <*> y
= \x y -> (+) <$> x <*> y

【讨论】:

    【解决方案2】:

    liftA2 可能比(&lt;*&gt;) 更容易混淆。

    (+) :: Int -> Int -> Int
    liftA2 (+) :: [Int] -> [Int] -> [Int]
    liftA2 (liftA2 (+)) :: Maybe [Int] -> Maybe [Int] -> Maybe [Int]
    
    liftA2 (liftA2 (+)) (Just [1,2,3]) (Just [4,5,6])
    

    【讨论】:

    • 你还能展示底层的构图吗,因为这是我特别想理解的。到目前为止,我只找到了带有 Compose 抽象的示例。
    • 你所说的“底层组合”是什么意思?
    • 我的意思是(liftA2 . liftA2) (+) = \a b -&gt; (\a' b' -&gt; (+) &lt;$&gt; a' &lt;*&gt; b') &lt;$&gt; a &lt;*&gt; b。谢谢!
    【解决方案3】:

    两个Applicatives 的组合始终是一个Applicative(与Monad 的情况不同)。

    我们可以利用来自Data.Functor.ComposeCompose newtype 来发挥我们的优势:

    newtype Compose f g a = Compose { getCompose :: f (g a) }
    

    它需要一些包装,但这种解决方案在适当的情况下可能很有用:

    example :: Maybe [Int]
    example =
      getCompose ((+) <$> Compose (Just [1,2,3]) <*> Compose (Just [4,5,6]))
    

    【讨论】:

      【解决方案4】:

      另一种方法是使用ListT transformer。虽然在这种情况下它工作得很好,但由于某种原因,它是一个贬值的转换器,用红色标记为 “不推荐使用:这个转换器在大多数 monads 上无效”

      import Control.Monad.Trans.List
      
      doit :: (Int-> Int -> Int) -> Maybe [Int] -> Maybe [Int] -> Maybe [Int]
      doit f mt1 mt2 = runListT $ f <$> (ListT mt1) <*> (ListT mt2)
      
      λ> doit (+) (Just [1,2,3]) (Just [4,5,6])
      Just [5,6,7,6,7,8,7,8,9]
      

      【讨论】:

      • 我在尽可能多的情况下使用 applicative 来逃避 monad,但它仍然可以正常工作。
      • @bob 我认为这是需要牢记的。变形金刚使嵌套变平并允许您以最简单的形式使用&lt;$&gt;&lt;*&gt; 模式。
      • 一些值得一提的事情:(1) transformers 中的ListT 已被弃用,因为它只能与可交换单子一起正常工作,并且确实最好避免使用(尽管@ 987654328@ 恰好是可交换的)。 (2) 对于ListT 有用的用例,有完整的替代实现,例如this one。 (3) 不过,在这种情况下,我认为没有理由使用 ListT,因为我们可能只是写一些与 Compose 非常相似的东西:getCompose $ f &lt;$&gt; Compose mt1 &lt;*&gt; Compose mt2
      • @duplode 谢谢...太好了,我不知道。
      猜你喜欢
      • 2012-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-20
      • 1970-01-01
      • 1970-01-01
      • 2015-04-21
      • 1970-01-01
      相关资源
      最近更新 更多