monad 定义中的样板代码
Boilerplate code in the definition of a monad
自 Functor-Applicative-Monad Proposal 以来,Monad 是 Applicative 的子类,Applicative 又是 Functor 的子类。从数学上讲,这似乎是一个明智的选择,我对此没有任何问题。
然而,令我恼火的是,即使 fmap
和 pure
和 <*>
的相应法则被固定,也需要写下 Functor 和 Applicative 实例无论如何,单子法则。事实上,在上面的链接提案中,他们自己写道:“您可以通过添加以下代码从 Monad 中简单地派生这些实例”:
import Control.Applicative (Applicative(..))
import Control.Monad (liftM, ap)
instance Functor m where
fmap = liftM
instance Applicative m where
pure = {- move the definition of `return` from the `Monad` instance here -}
(<*>) = ap
在大多数教程中,您只会看到等式 pure=return
,然后人们会像以前一样在 Monad 实例中继续定义 return
。因此,实际上,为只想定义 Monad 实例的每个人添加了 10 行样板代码。
在数学中,通常也不是这样运算的。例如,在定义某个环的合成定律时,reader 通常不会再次明确提醒环是加法下阿贝尔群的特例,而阿贝尔群又是群的特例。无论如何,这在定义上是明确的。
因此我的问题是:尽管 Monad 是 Applicative 的子类很好,而 Applicative 又是 Functor 的子类,但从编译器插入样板代码而不是程序员?
文档中的建议适用于过去定义了 Monad
实例但没有 Applicative
实例的遗留类型。然后根据 现有的 Monad
定义这些实例 是使包再次编译的快速可靠的修复方法。
但是,这不是新代码应该采用的方式,因为 Monad
实际上是更高级的 class。推荐的方式是
派生 Functor
实例。始终只有一种可能的方法可以做到这一点,编译器可以为您完成。
{-# LANGUAGE DeriveFunctor #-}
data YourMonadType a = ...
deriving (Functor)
定义 Applicative
。这不是很自动,有时 <*>
可能有点违反直觉,但您会掌握它的窍门,直接实施实际上比 (<*>) = ap
.
更有效
instance Applicative YourMonadType where
pure = {- direct definition here -}
q <*> r = {- direct definition here -}
定义 Monad
。这根本不需要 return
(因为它是默认的 = pure
),只需要 >>=
.
instance Monad YourMonadType where
q >>= f = {- direct definition here -}
旁注:可以说 Monad
的方法实际上应该是 join
而不是 >>=
。虽然 return
和 >>=
产生了所有 Functor
和 Applicative
方法,但 join
是它们的“正交特征”。
在现代 GHC 中,也可以通过在 WrappedMonad
新类型上使用 via
策略来获得 Applicative
的派生实例:
{-# LANGUAGE DerivingVia, DeriveFunctor #-}
import Control.Applicative
data YourMonadType a = YourMonadType a a
deriving stock (Functor, Show)
deriving Applicative via (WrappedMonad YourMonadType)
instance Monad YourMonadType where
return x = YourMonadType x x
YourMonadType x y >>= f = YourMonadType fx fy
where YourMonadType fx _ = f x
YourMonadType _ fy = f y
ghci> YourMonadType (+1) (*2) <*> YourMonadType 10 20
YourMonadType 11 40
自 Functor-Applicative-Monad Proposal 以来,Monad 是 Applicative 的子类,Applicative 又是 Functor 的子类。从数学上讲,这似乎是一个明智的选择,我对此没有任何问题。
然而,令我恼火的是,即使 fmap
和 pure
和 <*>
的相应法则被固定,也需要写下 Functor 和 Applicative 实例无论如何,单子法则。事实上,在上面的链接提案中,他们自己写道:“您可以通过添加以下代码从 Monad 中简单地派生这些实例”:
import Control.Applicative (Applicative(..))
import Control.Monad (liftM, ap)
instance Functor m where
fmap = liftM
instance Applicative m where
pure = {- move the definition of `return` from the `Monad` instance here -}
(<*>) = ap
在大多数教程中,您只会看到等式 pure=return
,然后人们会像以前一样在 Monad 实例中继续定义 return
。因此,实际上,为只想定义 Monad 实例的每个人添加了 10 行样板代码。
在数学中,通常也不是这样运算的。例如,在定义某个环的合成定律时,reader 通常不会再次明确提醒环是加法下阿贝尔群的特例,而阿贝尔群又是群的特例。无论如何,这在定义上是明确的。
因此我的问题是:尽管 Monad 是 Applicative 的子类很好,而 Applicative 又是 Functor 的子类,但从编译器插入样板代码而不是程序员?
文档中的建议适用于过去定义了 Monad
实例但没有 Applicative
实例的遗留类型。然后根据 现有的 Monad
定义这些实例 是使包再次编译的快速可靠的修复方法。
但是,这不是新代码应该采用的方式,因为 Monad
实际上是更高级的 class。推荐的方式是
派生
Functor
实例。始终只有一种可能的方法可以做到这一点,编译器可以为您完成。{-# LANGUAGE DeriveFunctor #-} data YourMonadType a = ... deriving (Functor)
定义
更有效Applicative
。这不是很自动,有时<*>
可能有点违反直觉,但您会掌握它的窍门,直接实施实际上比(<*>) = ap
.instance Applicative YourMonadType where pure = {- direct definition here -} q <*> r = {- direct definition here -}
定义
Monad
。这根本不需要return
(因为它是默认的= pure
),只需要>>=
.instance Monad YourMonadType where q >>= f = {- direct definition here -}
旁注:可以说
Monad
的方法实际上应该是join
而不是>>=
。虽然return
和>>=
产生了所有Functor
和Applicative
方法,但join
是它们的“正交特征”。
在现代 GHC 中,也可以通过在 WrappedMonad
新类型上使用 via
策略来获得 Applicative
的派生实例:
{-# LANGUAGE DerivingVia, DeriveFunctor #-}
import Control.Applicative
data YourMonadType a = YourMonadType a a
deriving stock (Functor, Show)
deriving Applicative via (WrappedMonad YourMonadType)
instance Monad YourMonadType where
return x = YourMonadType x x
YourMonadType x y >>= f = YourMonadType fx fy
where YourMonadType fx _ = f x
YourMonadType _ fy = f y
ghci> YourMonadType (+1) (*2) <*> YourMonadType 10 20
YourMonadType 11 40