<*> 是如何派生自 pure and (>>=) 的?

How does <*> derived from pure and (>>=)?

class Applicative f => Monad f where
       return :: a -> f a
       (>>=) :: f a -> (a -> f b) -> f b

(<*>) 可以从 pure and (>>=):

推导出来
fs <*> as =
    fs >>= (\f -> as >>= (\a -> pure (f a)))

对于行

fs >>= (\f -> as >>= (\a -> pure (f a)))

我对 >>= 的用法感到困惑。我认为它需要一个函子 f a 和一个函数,然后 return 另一个函子 f b。但是在这种表情中,我感到迷茫。

Applicative 是一个 Functor。 Monad 也是一个 Functor。我们可以看到 "Functorial" 值代表其“包含”的计算 ⁄ 产生 pure 值(如 IO aMaybe a, [] a, 等等), 作为各种计算的⁄隐喻的寓言。

函子描述 ⁄ 表示概念 ⁄ 计算类型,而函子值是具体化的计算,即“运行” ⁄ 解释 单独 步骤因此类似于著名的附加 间接 步骤,据称可以解决任何计算问题。

fsas 都是您的函子值,bind(>>=),或 do 符号 <-) “获取”函子中的进位值。 绑定 虽然属于 Monad。

我们可以在 Monad 中实现什么(使用 return 作为 pure 的同义词)

do { f <- fs ;       -- fs >>= ( \ f ->     -- fs  :: F (a -> b)   -- f :: a -> b
     a <- as ;       -- as >>= ( \ a ->     -- as  :: F  a         -- a :: a
     return (f a)    -- return (f a) ) )    -- f a ::         b
   }                                        -- ::     F       b

( 或 MonadComprehensions,

    [ f a | f <- fs, a <- as ]

),我们从 Applicative 的 <*> 中得到,它表达了相同的计算组合,但没有 Monad 的全部功能。 ,Applicative as 不依赖于那里的值 f,由 fs 表示的计算“产生”。 Monadic Functors 允许这种依赖,

    [ bar x y | x <- xs, y <- foo x ]

但 Applicative Functors 禁止这样做。

对于 Applicative,所有“计算”(如 fsas)必须“提前”知道;使用 Monad 它们可以被计算——纯粹地——基于前面“计算步骤”的结果(就像 foo x 正在做的:for (each) value x computation xs will produce, new computation foo x will (purely) calculated, the computation that将依次产生(一些)y(s)。


如果您想查看类型在 >>= 表达式中的对齐方式,这里是您的表达式及其子表达式的命名,因此可以用它们的类型进行注释,

exp = fs >>= g                                -- fs >>= 
      where  g f = xs >>= h                   --  (\ f -> xs >>=
                   where  h x = return (f x)  --           ( \ x -> pure (f x) ) )

 x   ::    a
 f   ::    a -> b
 f x ::         b
 return (f x) ::      F b
 h   ::    a ->       F b    -- (>>=) :: F a -> (a -> F b) -> F b
 xs  :: F  a                 --          xs     h
                             --           <-----
 xs >>= h ::          F b
 g f ::               F b
 g   ::   (a -> b) -> F b   -- (>>=) :: F (a->b) -> ((a->b) -> F b) -> F b
 fs  :: F (a -> b)          --          fs          g
                            --           <----------
 fs >>= g ::          F b
 exp ::               F b

两个 (>>=) 应用程序的类型适合:

 (fs :: F (a -> b))  >>=  (g :: (a -> b) -> F b)) :: F b
 (xs :: F  a      )  >>=  (h :: (a       -> F b)) :: F b

因此,整体类型确实

foo :: F (a -> b) -> F a -> F b
foo fs xs = fs >>= g                   -- foo = (<*>)
            where  g f = xs >>= h 
                         where  h x = return (f x)

最后我们可以把monadic bind看作是do的一个实现,对待do表示法

     do {

抽象地,公理地,由形式的行组成

           a <- F a ;
           b <- F b ;
           ......
           n <- F n ;
           return (foo a b .... n)
        }

(其中 aF b 等表示相应 类型 ),例如它描述了 F t 类型的整体组合计算,其中 foo :: a -> b -> ... -> n -> t。当 <- 右侧表达式的 none 不依赖于前面左侧的变量时,它本质上不是 Monadic,而只是这个 do 块的应用计算正在描述。

由于 Monad 法则,仅用两行 <- 就足以定义 do 块的含义。对于函子,只允许一行 <- (fmap f xs = do { x <- xs; return (f x) }).

因此,Functors/Applicative Functors/Monads 嵌入式领域特定语言,因为计算描述本身就是[=95=我们语言的 ]values(do 符号中箭头右侧的值)。


最后,送你一个类型曼陀罗:

                  T    a
                  T   (a  ->     b)
                      (a  ->  T  b)
                 -------------------
                  T          (T  b)
                 -------------------
                  T              b

这包含三合一:

       F   a                    A    a                  M   a
           a  ->  b             A   (a -> b)                a  ->  M  b
      --------------           --------------          -----------------
       F          b             A         b             M             b

让我们从我们正在实现的类型开始:

(<*>) :: Monad f => f (a -> b) -> f a -> f b

(正常类型<*>当然有Applicative约束,但这里我们尝试用Monad来实现Applicative)

所以在fs <*> as = _中,fs是一个"f of functions"(f (a -> b)),而as是一个"f of as".

我们将从绑定开始 fs:

(<*>) :: Monad f => f ( a -> b) -> f a -> f b
fs <*> as
  = fs >>= _

如果你真的编译它,GHC 会告诉我们漏洞 (_) 的类型:

foo.hs:4:12: warning: [-Wtyped-holes]
    • Found hole: _ :: (a -> b) -> f b
      Where: ‘a’, ‘f’, ‘b’ are rigid type variables bound by
               the type signature for:
                 (Main.<*>) :: forall (f :: * -> *) a b.
                               Monad f =>
                               f (a -> b) -> f a -> f b
               at foo.hs:2:1-45

有道理。 Monad 的 >>= 在左边接受一个 f a,在右边接受一个函数 a -> f b,所以通过在左边绑定一个 f (a -> b),右边的函数接收一个 (a -> b) 函数 "extracted" 来自 fs。如果我们可以编写一个函数,可以将它用于 return 和 f b,那么整个绑定表达式将 return f b 我们需要满足类型签名的 <*>.

所以它看起来像:

(<*>) :: Monad f => f ( a -> b) -> f a -> f b
fs <*> as
  = fs >>= (\f -> _)

我们在那里可以做什么?我们有f :: a -> b,还有as :: f a,我们需要做一个f b。如果您习惯 Functor,那很明显;只是 fmap f asMonad 意味着 Functor,所以这确实有效:

(<*>) :: Monad f => f ( a -> b) -> f a -> f b
fs <*> as
  = fs >>= (\f -> fmap f as)

我认为, 更容易理解 Applicative 可以使用 Monad 中的工具实现的方式。

那么为什么您的示例使用另一个 >>=pure 而不是 fmap 来编写?这有点让人想起 Monad 没有 ApplicativeFunctor 作为超类的日子。 Monad 总是 "morally" 暗示这两者(因为您可以仅使用 Monad 的功能实现 ApplicativeFunctor),但是 Haskell 没有总是需要有这些实例,这导致书籍、教程、博客文章等解释 如何 仅使用 Monad 来实现这些。给出的示例行只是根据 >>=pure (return)1.[= 内联 fmap 的定义91=]

我会像没有fmap一样继续解压,这样就会导致你混淆的版本。

如果我们不打算使用 fmap 来组合 f :: a -> bas :: f a,那么我们需要绑定 as 以便我们有一个表达式属于 a 类型以将 f 应用于:

(<*>) :: Monad f => f ( a -> b) -> f a -> f b
fs <*> as
  = fs >>= (\f -> as >>= (\a -> _))

我们需要在那个洞里面做一个 f b,我们有 f :: a -> ba :: af a 给我们一个 b,所以我们需要调用 pure 把它变成一个 f b:

(<*>) :: Monad f => f ( a -> b) -> f a -> f b
fs <*> as
  = fs >>= (\f -> as >>= (\a -> pure (f a)))

这就是这条线的作用。

  1. 绑定 fs :: f (a -> b) 以访问 f :: a -> b
  2. 在可以访问 f 的函数内部,它绑定 as 以访问 a :: a
  3. 在可以访问 a 的函数内部(它仍然在可以访问 f 的函数内部),调用 f a 来创建一个 b,并对结果调用 pure 使其成为 f b

1 你可以使用>>=pure实现fmap作为fmap f xs = xs >>= (\x -> pure (f x)),也就是fmap f xs = xs >>= pure . f ].希望您能看到示例的内部绑定只是内联第一个版本。

您可以根据 (>>=)return 定义 (<*>),因为所有 monad 都是应用函子。您可以在 Functor-Applicative-Monad Proposal 中阅读更多相关信息。特别是,pure = return(<*>) = ap 是在给定现有 Monad 定义的情况下实现应用定义的最短方法。

查看 (<*>)ap(>>=) 的类型签名:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b
ap    :: Monad       m => m (a -> b) -> m a -> m b
(>>=) :: Monad       m => m a -> (a -> m b) -> m b

(<*>)ap 的类型签名几乎相同。由于 ap 是使用 do 表示法编写的,因此它等同于 (>>=) 的某些用法。我不确定这是否有帮助,但我发现 ap 的定义可读。这是一个重写:

  ap m1 m2 = do { x1 <- m1; x2 <- m2; return (x1 x2) }
≡ ap m1 m2 = do
    x1 <- m1
    x2 <- m2
    return (x1 x2)
≡ ap m1 m2 =
    m1 >>= \x1 ->
    m2 >>= \x2 ->
    return (x1 x2)
≡ ap m1 m2 = m1 >>= \x1 -> m2 >>= \x2 -> return (x1 x2)
≡ ap mf ma = mf >>= (\f -> ma >>= (\a -> pure (f a)))

这是你的定义。您可以证明此定义支持 applicative functor laws,因为并非根据 (>>=)return 定义的所有内容都如此。