在“向大家学习哈斯克尔!”作者声称Applicative IO
实例是这样实现的:
instance Applicative IO where
pure = return
a <*> b = do
f <- a
x <- b
return (f x)
我可能错了,但似乎return
和do
特定结构(某些含糖的(>>=)
绑定)来自Monad IO
。假设这是正确的,我的实际问题是:
为什么Applicative IO
实施取决于Monad IO
函数/组合器?
与Applicative
相比,Monad
是否更强大概念?
修改(一些澄清):
这种实现违背了我的直觉,因为根据Typeclassopedia的文章,在之前,某个类型需要Applicative
才能生成Monad
(或者它 理论上)。
答案 0 :(得分:12)
(...)根据Typeclassopedia文章,在给定类型为Monad之前,它是必需的(或者它应该是理论上的。)
是的,你的括号旁边就是这里的问题。理论上,任何Monad
也应该是Applicative
,但由于历史原因(因为Monad
已经存在更长时间),实际上并不需要这样做。这也不是Monad
的唯一特性。
考虑相关类型类的实际定义,取自base
软件包在Hackage上的来源。
这是Applicative
:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
......我们可以观察以下内容:
Functor
。同时,这里是Monad
:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
......我们可以观察以下内容:
Applicative
,还忽略了Functor
,Monad
逻辑上暗示了这两者,但没有明确要求。return
和join
在数学上更自然的定义。fail
,它根本不适合。一般来说,Monad
类型类与它所基于的数学概念不同的方式可以追溯到它作为编程抽象的历史。有些像它与Applicative
共享的函数应用程序偏差一样,是功能语言中存在的反映;其他人,如fail
或缺乏适当的阶级背景,最重要的是历史事故。
归结为具有Monad
实例的意味着Applicative
的实例,而后者又暗示了Functor
的实例。类上下文只是明确地将其形式化;无论如何都是如此。就目前而言,给定Monad
实例,Functor
和Applicative
都可以以完全通用的方式定义。 Applicative
的“功能不如Monad
完全相同,而且一般:如果您复制,Monad
会自动Applicative
+粘贴通用实例,但存在Applicative
个实例,无法将其定义为Monad
。
类Functor f => Applicative f
这样的类上下文说了两件事:后者暗示了前者,并且必须存在一个定义来实现这个含义。在许多情况下,定义后者无论如何都隐含地定义了前者,但编译器通常不能推断出这种情况,因此需要明确地写出两个实例。使用Eq
和Ord
可以观察到同样的事情 - 后者显然暗示前者,但您仍然需要定义Eq
实例以便为{{1}定义一个实例}。
答案 1 :(得分:9)
IO
类型在Haskell中是抽象的,因此如果要为Applicative
实现通用IO
,则必须使用IO支持的操作。由于您可以根据Applicative
操作实施Monad
,这似乎是一个不错的选择。你能想到另一种实现它的方法吗?
是的,Applicative
在某种程度上不如Monad
强大。
答案 2 :(得分:7)
是不是应用较少强大的概念而不是Monad?
是的,因此只要您有Monad
,就可以随时将其设为Applicative
。您可以将IO
替换为示例中的任何其他monad,它将是一个有效的Applicative
实例。
作为类比,虽然彩色打印机可能比灰度打印机更强大,但您仍然可以使用彩色打印机打印灰度图像。
当然,也可以将Monad
实例基于Applicative
并设置return = pure
,但您通常无法定义>>=
。这就是Monad
更强大的意思。
答案 3 :(得分:3)
在perfect world中,Monad
每个Applicative
都是class Applicative a => Monad a where ...
(因此我们有{{1}}),但由于历史原因,这两个类型都是独立的。所以你观察到这个定义有点“向后”(使用更强大的抽象来实现功能较弱的定义)是正确的。
答案 4 :(得分:1)
对于旧版本的GHC,您已经拥有完美的答案,但在最新版本中,您确实拥有class Applicative m => Monad m
,因此您的问题需要另一个答案。
就GHC实现而言:GHC只是在尝试编译任何实例之前检查为给定类型定义的实例。
就代码语义而言:class Applicative m => Monad m
并不意味着必须“首先”定义Applicative
实例,只是如果它没有在程序结束时定义,那么编译器将中止。