这是一个一般性问题,与任何一段代码无关。
假设你有一个类型T a
,它可以被赋予Monad
的一个示例,由于每个单子都是一个Applicative
,通过赋值pure = return
和(<*>) = ap
,然后每个应用式都是一个Functor
,通过fmap f x = pure f <*> x
,最好是先定义Monad
的示例,然后简单地给予Applicative
和Functor
的T
示例
我觉得这有点落后。如果我是在做数学而不是编程,我会想我会首先证明我的对象是一个函子,然后继续添加限制,直到我也证明它是一个单子。我知道Haskell只是受到了范畴论的启发,很明显,构造证明时使用的技术不是编写有用程序时使用的技术。但是我想听听Haskell社区的意见。是从Monad
降到Functor
更好呢?还是从Functor
升到Monad
更好呢?
5条答案
按热度按时间0mkxixxg1#
我倾向于先写和看写
Functor
示例。加倍如此是因为如果你使用LANGUAGE DeriveFunctor
pragma,那么data Foo a = Foo a deriving ( Functor )
大部分时间都能工作。当
Applicative
可以比Monad
更通用时,棘手的地方在于示例的一致性。上面我按照
Functor
到Monad
的顺序定义了这些示例,并且,单独来看,每个示例都是正确的。不幸的是,Applicative
和Monad
示例并不对齐:ap
和(<*>)
是显著不同的,正如(>>)
和(*>)
是显著不同的。出于敏感性的考虑,尤其是当每个人都掌握了应用性/单子提议时,它们应该保持一致。
但最后,您可能能够仔细地梳理
Applicative
和Monad
之间的差异,以便它们以良性的方式表现不同---例如具有更懒惰或更高效的Applicative
示例。这种情况实际上相当频繁地发生,我觉得陪审团对于“良性”的含义以及在何种“观察”下仍然有一点不清楚。也许Facebook的Haxl项目中使用这种方法最多,其中Applicative
示例比Monad
示例 * 更并行 *,因此效率更高,但代价是一些相当严重的“未观察到的”副作用。在任何情况下,如果它们不同,请记录下来。
r8uurelv2#
与Abrahamson的回答相比,我经常选择相反的方法,我只手动定义
Monad
示例,并借助Control.Monad
中已经定义的函数,根据它定义Applicative
和Functor
,这使得这些示例对于任何单子都是相同的,即:虽然
Functor
和Applicative
的定义总是“无脑的”,并且很容易推理,但是我必须注意到这不是最终的解决方案,因为在某些情况下,示例可以更有效地实现,甚至提供新的特性。而Monad
示例由于单子的性质而只能顺序地执行它们。vlju58qv3#
Functor
示例的定义通常非常简单,我通常会手动完成这些操作。对于
Applicative
和Monad
,这要看情况。pure
和return
通常都很简单,你把扩展的定义放在哪个类中并不重要。对于bind,有时候走“分类的路”是有益的,即首先定义专用的join' :: (M (M x)) -> M x
,然后定义a>>=b = join' $ fmap b a
(当然,如果根据>>=
定义fmap
,这就不起作用了)。那么,对于Applicative
示例来说,重用(>>=)
可能是有用的。其他时候,
Applicative
示例可以很容易地编写,或者比一般的Monad派生实现更有效。在这种情况下,您应该明确地单独定义<*>
。nfeuvbwi4#
The magic here, that the Haskell uses the Kleisli-tiplet notation of a monad, that is more convenient way, if somebody wants to use monads in imperative programming like tools.
I asked the same question, and the answer come after a while, if you see the definitions of the Functor, Applicative, Monad in haskell you miss one link, which is the original definition of the monad, which contains only the join operation, that can be found on the HaskellWiki .
With this point of view you will see how haskell monads are built up functor, applicative functors, monads and Kliesli triplet.
A rough explanation can be found here: https://github.com/andorp/pearls/blob/master/Monad.hs And other with the same ideas here: http://people.inf.elte.hu/pgj/haskell2/jegyzet/08/Monad.hs
vs91vp4v5#
我想你误解了子类在Haskell中的工作方式。它们不像OO子类!相反,子类约束,如
说明“任何具有规范
Monad
结构的类型也必须具有规范Applicative
结构”。放置这样的约束有两个基本原因:例如,请考虑:
在
Norm
上的第一个超类约束出现是因为赋范空间的概念非常弱,除非你也假设一个向量空间结构;第二个问题是因为(给定一个向量空间)一个Norm
导出一个Metric
,你可以通过观察证明是 * 任何 *
V
的有效Metric
示例,具有有效Vector
示例和有效norm
函数。我们说范数 * 导出 * 度量。请参阅http://en.wikipedia.org/wiki/Normed_vector_space#Topological_structure。Monad
上的Functor
和Applicative
超类类似于Metric
,而不类似于Vector
:来自Monad
的return
和>>=
函数诱导出Functor
和Applicative
结构:fmap
:可定义为fmap f a = a >>= return . f
,在Haskell 98标准库中为liftM
。pure
:与return
的操作相同;这两个名称是Applicative
不是Monad
的超类时遗留下来的。<*>
:可定义为af <*> ax = af >>= \ f -> ax >>= \ x -> return (f x)
,在Haskell 98标准库中为liftM2 ($)
。join
:可以定义为join aa = aa >>= id
。因此,从数学上讲,用
Monad
来定义Functor
和Applicative
运算是非常明智的。