<*>如何从pure和(>> =)派生?

时间:2018-08-22 06:20:46

标签: haskell monads functor applicative

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

(<*>)可以源自pure和(>>=)

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

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

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

3 个答案:

答案 0 :(得分:7)

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

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

<*>的普通类型当然具有Applicative约束,但是这里我们试图使用Monad来实现Applicative

因此,在fs <*> as = _中,fs是“ f个函数”(f (a -> b)),而as是“ f个a s”。

我们将首先绑定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)“提取”了{1}}函数。并且只要我们可以编写一个函数来返回fs,然后整个绑定表达式将返回我们需要满足f b的类型签名的f b

因此它看起来像:

<*>

我们在那里可以做什么?我们有(<*>) :: Monad f => f ( a -> b) -> f a -> f b fs <*> as = fs >>= (\f -> _) ,还有f :: a -> b,我们需要制作一个as :: f a。如果您习惯了f b,那就很明显了;仅Functorfmap f as暗含Monad,因此实际上可以做到这一点:

Functor

我认为,这也是一种更容易的方法,用于理解可以使用(<*>) :: Monad f => f ( a -> b) -> f a -> f b fs <*> as = fs >>= (\f -> fmap f as) 中的功能来普遍实现Applicative的方式。

那么为什么您的示例使用其他Monad>>=而不是pure编写?可以回想起fmap并非 具有MonadApplicative作为超类的时代。 Functor总是“道德上”暗示这两者(因为您可以仅使用Monad的功能来实现ApplicativeFunctor),但是Haskell并不总是要求这样做这些实例会导致书籍,教程,博客文章等说明如何仅使用Monad来实现这些实例。给出的示例行仅用Monadfmap>>=){sup> 1 内联pure的定义。

我将继续解压缩,就像我们没有return一样,以便打开您所迷惑的版本。

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

f

在该孔内,我们需要制作一个(<*>) :: Monad f => f ( a -> b) -> f a -> f b fs <*> as = fs >>= (\f -> as >>= (\a -> _)) ,我们有f bf :: a -> ba :: a给了我们f a,所以我们需要调用b才能将其变成pure

f b

这就是这行的内容。

  1. 绑定(<*>) :: Monad f => f ( a -> b) -> f a -> f b fs <*> as = fs >>= (\f -> as >>= (\a -> pure (f a))) 以访问fs :: f (a -> b)
  2. 在有权访问f :: a -> b的函数中,它绑定f以访问as
  3. 在有权访问a :: a的函数内(仍然在有权访问a的函数内),调用f来制作f a,并在结果上调用b,使其成为pure

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

答案 1 :(得分:3)

Applicative是函子。 Monad还是函子。我们可以看到“函数值”代表这些值的计算(例如IO aMaybe a[] a等)。

fsas都是您的函数值,并且 bind (>>=)do表示法<-)在函子中“获取”进位值。 Bind 属于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 ]

),我们来自应用程序的<*>,它表示相同的计算组合,但没有Monad的全部功能。区别在于,应用as不依赖于那里的值f,是由计算fs“产生”的。 Monad允许这种依赖,

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

但Applicative禁止使用。

对于Applicative,所有“计算”(例如fsas)必须“事先”知道;使用Monad可以根据之前的“计算步骤”的结果来计算它们(例如foo x所做的:对于(每个) value x计算 xs将会产生,新的计算foo x将被(完全)计算,从而依次产生(某些)y(个)。


如果您想查看类型在>>=表达式中的对齐方式,以下是您的表达式及其子表达式的名称,以便可以使用其类型对其进行注释,

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绑定视为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。而且,当<-的右侧表达式都不依赖于前面的左侧变量时,它本质上不是Monadic,而只是此do块所描述的应用计算。 / p>

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

因此,函子/应用函子/单子是EDSL,是嵌入式域特定的语言,因为计算描述本身就是我们语言的(在右侧) do标记中的箭头)。


最后,为您输入一个曼荼罗:

                  M    a
                      (a  ->  M  b)
                  M          (M  b)
                  M              b

这包含三合一:

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

答案 2 :(得分:1)

您可以根据(<*>)(>>=)来定义return,因为所有单子都是可应用的仿函数。您可以在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定义的内容都可以做到这一点。