本文介绍了Monad定义中的样板代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!
问题描述
由于Functor-Applicative-Monad Proposal,Monad是应用类的子类,而应用类又是函数子类。从数学上讲,这似乎是一个明智的选择,我对此没有任何问题。
然而,令我恼火的是,即使fmap
和pure
和<*>
的相应定律无论如何都是由单子定律固定的,也需要写下函数式和应用实例。事实上,在上面的链接提案中,它们自己写道:&您只需添加以下代码即可从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行样板代码。
在数学中,人们通常也不会这样做。例如,当定义某个环的合成律时,通常不会再次明确地提醒读者环是加法下的阿贝尔群的特殊实例,而阿贝尔群又是群的特殊实例。无论如何,这在定义上是明确的。
因此我的问题是:即使monad是Applicative的子类,而Applicative又是Functor的子类,这种情况不是可以在后台发生吗,因为编译器插入的是样板代码,而不是程序员?
推荐答案
文档中的建议是针对过去定义了Monad
实例但没有定义Applicative
实例的遗留类型。然后根据现有的Monad
定义这些实例,这是使包重新编译的一种快速可靠的修复方法。
Monad
实际上是更高级的类。推荐的方式是派生
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
Newtype:
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
这篇关于Monad定义中的样板代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!