这是一个普遍的问题,与任何一段代码无关。
假设您有一个T a
类型,可以为其提供Monad
个实例。由于每个monad都是Applicative
,因为我分配pure = return
和(<*>) = ap
,然后每个应用都是Functor
来自fmap f x = pure f <*> x
,定义您的实例是否更好?首先Monad
,然后平凡地提供T
和Applicative
的{{1}}个实例?
对我来说感觉有点落后。如果我在做数学而不是编程,我会认为我会先显示我的对象是一个仿函数,然后继续添加限制,直到我也将它显示为monad。我知道Haskell只是受到类别理论的启发,显然在构建证明时使用的技术不是编写有用程序时会使用的技术,但我想从Haskell社区获得意见。从Functor
到Monad
是否更好?或者从Functor
到Functor
?
答案 0 :(得分:25)
我倾向于编写并首先编写Functor
实例。这是因为如果您使用LANGUAGE DeriveFunctor
编译指示,那么data Foo a = Foo a deriving ( Functor )
大部分时间都可以使用。
当Applicative
比Monad
更为通用时,棘手的问题就是实例协议。例如,这是Err
数据类型
data Err e a = Err [e] | Ok a deriving ( Functor )
instance Applicative (Err e) where
pure = Ok
Err es <*> Err es' = Err (es ++ es')
Err es <*> _ = Err es
_ <*> Err es = Err es
Ok f <*> Ok x = Ok (f x)
instance Monad (Err e) where
return = pure
Err es >>= _ = Err es
Ok a >>= f = f a
上面我定义了Functor
- 到 - Monad
顺序中的实例,并且单独考虑每个实例都是正确的。很遗憾,Applicative
和Monad
个实例未对齐:ap
和(<*>)
与(>>)
和(*>)
明显不同。
Err "hi" <*> Err "bye" == Err "hibye"
Err "hi" `ap` Err "bye" == Err "hi"
出于敏感目的,特别是一旦申请人/ Monad提案在每个人手中,这些都应该一致。如果您定义了instance Applicative (Err e) where { pure = return; (<*>) = ap }
,则他们将对齐。
但是,最后,你可能有能力仔细挑逗Applicative
和Monad
中的差异,以便它们以良性方式表现出不同的行为 - 例如拥有更加懒惰或更有效率{ {1}}实例。这实际上经常发生,我觉得陪审团仍然有点“良性”意味着什么,以及你的实例应该在什么样的“观察”。也许最合理的一些用途是在Facebook的Haxl项目中,Applicative
实例比Applicative
实例更加并行化,因此效率更高一些相当严重的“未观察到的”副作用的代价。
在任何情况下,如果它们不同,请记录下来。
答案 1 :(得分:5)
与Abrahamson的答案相比,我经常选择反向方法。我手动定义Monad
实例,并在Applicative
中已定义的函数的帮助下定义Functor
和Control.Monad
,这使得这些实例相同绝对任何monad,即:
instance Applicative SomeMonad where
pure = return
(<*>) = ap
instance Functore SomeMonad where
fmap = liftM
虽然这样Functor
和Applicative
的定义总是“无脑”并且很容易推理,但我必须指出,这不是最终的解决方案,因为有些情况,何时可以更有效地实施实例,甚至提供新功能。例如,Concurrently
的Applicative
实例同时执行...而Monad
实例只能由于自然monad而按顺序执行它们。
答案 2 :(得分:3)
Functor
个实例通常很容易定义,我通常会手工完成。
取决于Applicative
和Monad
。 pure
和return
通常同样容易,并且在扩展定义的哪个类中无关紧要。对于绑定,有时候走“分类方式”是合适的,即首先定义一个专门的join' :: (M (M x)) -> M x
,然后定义a>>=b = join' $ fmap b a
(如果你已经定义了fmap
,这当然不会有效。 >>=
)的条款。然后,对(>>=)
实例重复使用Applicative
可能很有用。
其他时候,Applicative
实例可以很容易地编写or is more efficient than the generic Monad-derived implementation。在这种情况下,您应该明确定义<*>
。
答案 3 :(得分:0)
这里的神奇之处在于,Haskell使用了monad的Kleisli-tiplet符号 更方便的方法,如果有人想在工具等命令式编程中使用monad。
我问了同样的问题,如果你看到定义,答案会在一段时间后出现 在haskell中 Functor , Applicative , Monad 你错过了一个链接,这是monad的原始定义,其中只包含加入操作,可在HaskellWiki上找到。
从这个角度来看,你会看到haskell monad是如何构建的functor,applicative functors,monads和Kliesli triplet。
可在此处找到一个粗略的解释:https://github.com/andorp/pearls/blob/master/Monad.hs 其他人也有同样的想法:http://people.inf.elte.hu/pgj/haskell2/jegyzet/08/Monad.hs
答案 4 :(得分:0)
我认为你错误地理解子类在Haskell中是如何工作的。他们不像OO子类!相反,是一个子类约束,比如
class Applicative m => Monad m
说“任何具有规范Monad
结构的类型也必须具有规范Applicative
结构”。您可以使用以下两个基本原因来设置约束:
例如,考虑:
class Vector v where
(.^) :: Double -> v -> v
(+^) :: v -> v -> v
negateV :: v -> v
class Metric a where
distance :: a -> a -> Double
class (Vector v, Metric v) => Norm v where
norm :: v -> Double
Norm
上的第一个超类约束产生了,因为除非你还假设一个向量空间结构,否则赋范空间的概念确实很弱;第二个出现是因为(给定一个向量空间)Norm
会导致Metric
,你可以通过观察
instance Metric V where
distance v0 v1 = norm (v0 .^ negateV v1)
是具有有效Metric
实例和有效V
函数的任何 Vector
的有效norm
实例。我们说规范会导致一个指标。请参阅http://en.wikipedia.org/wiki/Normed_vector_space#Topological_structure。
Functor
上的Applicative
和Monad
超类与Metric
类似,而不是Vector
:return
和{{1来自>>=
的函数会导致Monad
和Functor
结构:
Applicative
:可以定义为fmap
,在Haskell 98标准库中为fmap f a = a >>= return . f
。liftM
:与pure
的操作相同;当return
不是Applicative
的超类时,这两个名称是遗产。Monad
:可以定义为<*>
,在Haskell 98标准库中为af <*> ax = af >>= \ f -> ax >>= \ x -> return (f x)
。liftM2 ($)
:可以定义为join
。因此,用join aa = aa >>= id
来定义Functor
和Applicative
操作在数学上是完全合理的。