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
。但是在这种表达中,我感到迷茫。
答案 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
,那就很明显了;仅Functor
。 fmap f as
暗含Monad
,因此实际上可以做到这一点:
Functor
我认为,这也是一种更容易的方法,用于理解可以使用(<*>) :: Monad f => f ( a -> b) -> f a -> f b
fs <*> as
= fs >>= (\f -> fmap f as)
中的功能来普遍实现Applicative
的方式。
那么为什么您的示例使用其他Monad
和>>=
而不是pure
编写?可以回想起fmap
并非 具有Monad
和Applicative
作为超类的时代。 Functor
总是“道德上”暗示这两者(因为您可以仅使用Monad
的功能来实现Applicative
和Functor
),但是Haskell并不总是要求这样做这些实例会导致书籍,教程,博客文章等说明如何仅使用Monad
来实现这些实例。给出的示例行仅用Monad
和fmap
(>>=
){sup> 1 内联pure
的定义。
我将继续解压缩,就像我们没有return
一样,以便打开您所迷惑的版本。
如果我们不打算使用fmap
来组合fmap
和f :: a -> b
,那么我们将需要绑定as :: f a
以便具有类型表达式as
将a
应用于:
f
在该孔内,我们需要制作一个(<*>) :: Monad f => f ( a -> b) -> f a -> f b
fs <*> as
= fs >>= (\f -> as >>= (\a -> _))
,我们有f b
和f :: a -> b
。 a :: a
给了我们f a
,所以我们需要调用b
才能将其变成pure
:
f b
这就是这行的内容。
(<*>) :: Monad f => f ( a -> b) -> f a -> f b
fs <*> as
= fs >>= (\f -> as >>= (\a -> pure (f a)))
以访问fs :: f (a -> b)
f :: a -> b
的函数中,它绑定f
以访问as
a :: a
的函数内(仍然在有权访问a
的函数内),调用f
来制作f a
,并在结果上调用b
,使其成为pure
1 您可以使用f b
和fmap
作为>>=
(也就是pure
)来实现fmap f xs = xs >>= (\x -> pure (f x))
。希望您可以看到示例的内部绑定只是内联第一个版本。
答案 1 :(得分:3)
Applicative是函子。 Monad还是函子。我们可以看到“函数值”代表这些值的计算(例如IO a
,Maybe a
,[] a
等)。
fs
和as
都是您的函数值,并且 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,所有“计算”(例如fs
或as
)必须“事先”知道;使用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)
}
(带有a
,F 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
定义的内容都可以做到这一点。