编写应用程序

Composing Applicatives

我正在阅读 haskellbook 的第 25 章(组合类型),希望更全面地理解应用组合

作者提供了一个类型来体现类型组合:

newtype Compose f g a =
  Compose { getCompose :: f (g a) }
  deriving (Eq, Show)

并为此类型提供仿函数实例:

instance (Functor f, Functor g) =>
  Functor (Compose f g) where
    fmap f (Compose fga) =
      Compose $ (fmap . fmap) f fga

但是 Applicative 实例作为练习留给 reader:

instance (Applicative f, Applicative g) =>
  Applicative (Compose f g) where
    -- pure :: a -> Compose f g a
    pure = Compose . pure . pure
    -- (<*>) :: Compose f g (a -> b)
    --       -> Compose f g a
    --       -> Compose f g b
    Compose fgf <*> Compose fgx = undefined

我可以作弊并在线查找答案... Data.Functor.Compose 的来源提供了应用实例定义:

Compose f <*> Compose x = Compose ((<*>) <$> f <*> x)

但我无法理解这里发生的事情。根据类型签名,fx 都包含在两层应用结构中。我似乎遇到的障碍是了解这个位的情况:(<*>) <$> f。我可能会有后续问题,但它们可能取决于该表达式的评估方式。是说 "fmap <*> over f" 还是 "apply <$> to f"?

请帮助对这里发生的事情有一个直观的了解。

谢谢! :)

考虑表达式 a <$> b <*> c。这意味着获取函数 a,并将其映射到函子 b,这将产生一个新的函子,然后将该新函子映射到函子 c.

首先,假设 a(\x y -> x + y)bJust 3cJust 5a <$> b 然后计算为 Just (\y -> 3 + y)a <$> b <*> c 然后计算为 Just 8

(如果前面的内容没有意义,那么在尝试理解多层应用程序之前,您应该尝试进一步理解单层应用程序。)

同样,在您的情况下,a(<*>)bfcx。如果您要为 fx 选择合适的值,您会发现它们也可以很容易地计算出来(尽管一定要保持您的图层不同;您的 (<*>) case 属于内部 Applicative,而 <$><*> 属于外部 Applicative。

而不是 <*>,您可以定义 liftA2

import Control.Applicative (Applicative (..))

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

instance (Applicative f, Applicative g) => Applicative (Compose f g) where
  pure a = Compose (pure (pure a))
  -- liftA2 :: (a -> b -> c) -> Compose f g a -> Compose f g b -> Compose f g c
  liftA2 f (Compose fga) (Compose fgb) = Compose _1

我们有 fga :: f (g a)fgb :: f (g b),我们需要 _1 :: f (g c)。由于 f 是适用的,我们可以使用 liftA2:

组合这两个值
  liftA2 f (Compose fga) (Compose fgb) = Compose (liftA2 _2 fga fgb)

现在我们需要

_2 :: g a -> g b -> g c

由于g也是适用的,我们也可以使用它的liftA2

  liftA2 f (Compose fga) (Compose fgb) = Compose (liftA2 (liftA2 f) fga fgb)

这种提升 liftA2 应用程序的模式对于其他事情也很有用。一般来说,

liftA2 . liftA2 :: (Applicative f, Applicative g) => (a -> b -> c) -> f (g a) -> f (g b) -> f (g c)

liftA2 . liftA2 . liftA2
  :: (Applicative f, Applicative g, Applicative h)
  => (a -> b -> c) -> f (g (h a)) -> f (g (h b)) -> f (g (h c))