首页 > 解决方案 > 为什么 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

标签: haskell

解决方案


这只有效,因为Snap 一个Monad实例(并且它实际上在那时的范围内)。

实际上,编译器在两个单独的过程中处理声明:首先它解析所有实例头

instance Applicative Snap
instance Monad Snap

...甚至没有查看实际的方法实现。这很好:Monad只要看到Applicative实例就很高兴。

所以它已经知道这Snap是一个单子。然后它继续对(<*>)实现进行类型检查,注意到它需要Monad实例,并且......是的,它就在那里,所以这也很好。

我们拥有的实际原因ap :: Monad m => ...主要是历史原因:Haskell98Monad类没有Applicative,甚至没有Functor超类,因此可以编写Monad m => ...不能使用fmapor的代码<*>。因此引入了liftMap函数作为替代。

然后,当建立更好的当前类层次结构时,通过引用已经存在的实例来简单地定义许多实例Monad,这对于所有事情来说已经足够了。

IMO 通常在编写实例之前<*>直接实现并且绝对是一个好主意,而不是相反。fmap Monad


推荐阅读