为什么ap在Applicative中可用?
我正在尝试为 Snap 实现 MonadUnliftIO 并分析 Snap 类。我发现 ap 用于实现 Applicative,而 ap 需要 Monad,Monad 需要 Applicative。它看起来像一个循环。
我认为直到现在不可能写出这样的东西。这种把戏的极限是什么?
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
class Applicative m => Monad m where
return :: a -> m a
instance Applicative Snap where
pure x = ...
(<*>) = ap
ap :: Monad m => m (a -> b) -> m a -> m b
回答
这只有效,因为Snap 有一个Monad实例(并且它实际上在当时的范围内)。
实际上,编译器在两个单独的过程中处理声明:首先它解析所有实例头
instance Applicative Snap
instance Monad Snap
...甚至没有查看实际的方法实现。效果很好:Monad只要它看到Applicative实例就很高兴。
那么它已经知道那Snap是一个 monad。然后它继续对(<*>)实现进行类型检查,注意到它需要Monad实例,并且......是的,它在那里,所以也很好。
我们拥有的实际原因ap :: Monad m => ...主要是历史原因:Haskell98Monad类没有Applicative或什Functor至没有超类,因此可能会编写Monad m => ...无法使用fmapor 的代码<*>。因此引入了liftM和ap函数作为替代。
然后,当建立了更好的当前类层次结构时,许多实例只是通过引用已经存在的Monad实例来定义的,这对于一切来说毕竟已经足够了。
IMO 通常在编写实例之前直接实现<*>并且绝对是一个好主意,而不是相反。fmap Monad