haskell 是用应用来定义函子更好,还是用单子来定义函子更好?

zbwhf8kr  于 2022-11-14  发布在  其他
关注(0)|答案(5)|浏览(166)

这是一个一般性问题,与任何一段代码无关。
假设你有一个类型T a,它可以被赋予Monad的一个示例,由于每个单子都是一个Applicative,通过赋值pure = return(<*>) = ap,然后每个应用式都是一个Functor,通过fmap f x = pure f <*> x,最好是先定义Monad的示例,然后简单地给予ApplicativeFunctorT示例
我觉得这有点落后。如果我是在做数学而不是编程,我会想我会首先证明我的对象是一个函子,然后继续添加限制,直到我也证明它是一个单子。我知道Haskell只是受到了范畴论的启发,很明显,构造证明时使用的技术不是编写有用程序时使用的技术。但是我想听听Haskell社区的意见。是从Monad降到Functor更好呢?还是从Functor升到Monad更好呢?

0mkxixxg

0mkxixxg1#

我倾向于先写和看写Functor示例。加倍如此是因为如果你使用LANGUAGE DeriveFunctor pragma,那么data Foo a = Foo a deriving ( Functor )大部分时间都能工作。
Applicative可以比Monad更通用时,棘手的地方在于示例的一致性。

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

上面我按照FunctorMonad的顺序定义了这些示例,并且,单独来看,每个示例都是正确的。不幸的是,ApplicativeMonad示例并不对齐:ap(<*>)是显著不同的,正如(>>)(*>)是显著不同的。

Err "hi" <*>  Err "bye" == Err "hibye"
Err "hi" `ap` Err "bye" == Err "hi"

出于敏感性的考虑,尤其是当每个人都掌握了应用性/单子提议时,它们应该保持一致。
但最后,您可能能够仔细地梳理ApplicativeMonad之间的差异,以便它们以良性的方式表现不同---例如具有更懒惰或更高效的Applicative示例。这种情况实际上相当频繁地发生,我觉得陪审团对于“良性”的含义以及在何种“观察”下仍然有一点不清楚。也许Facebook的Haxl项目中使用这种方法最多,其中Applicative示例比Monad示例 * 更并行 *,因此效率更高,但代价是一些相当严重的“未观察到的”副作用。
在任何情况下,如果它们不同,请记录下来。

r8uurelv

r8uurelv2#

与Abrahamson的回答相比,我经常选择相反的方法,我只手动定义Monad示例,并借助Control.Monad中已经定义的函数,根据它定义ApplicativeFunctor,这使得这些示例对于任何单子都是相同的,即:

instance Applicative SomeMonad where
  pure = return
  (<*>) = ap

instance Functore SomeMonad where
  fmap = liftM

虽然FunctorApplicative的定义总是“无脑的”,并且很容易推理,但是我必须注意到这不是最终的解决方案,因为在某些情况下,示例可以更有效地实现,甚至提供新的特性。而Monad示例由于单子的性质而只能顺序地执行它们。

vlju58qv

vlju58qv3#

Functor示例的定义通常非常简单,我通常会手动完成这些操作。
对于ApplicativeMonad,这要看情况。purereturn通常都很简单,你把扩展的定义放在哪个类中并不重要。对于bind,有时候走“分类的路”是有益的,即首先定义专用的join' :: (M (M x)) -> M x,然后定义a>>=b = join' $ fmap b a(当然,如果根据>>=定义fmap,这就不起作用了)。那么,对于Applicative示例来说,重用(>>=)可能是有用的。
其他时候,Applicative示例可以很容易地编写,或者比一般的Monad派生实现更有效。在这种情况下,您应该明确地单独定义<*>

nfeuvbwi

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

vs91vp4v

vs91vp4v5#

我想你误解了子类在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)

是 * 任何 * V的有效Metric示例,具有有效Vector示例和有效norm函数。我们说范数 * 导出 * 度量。请参阅http://en.wikipedia.org/wiki/Normed_vector_space#Topological_structure。
Monad上的FunctorApplicative超类类似于Metric,而不类似于Vector:来自Monadreturn>>=函数诱导出FunctorApplicative结构:

  • 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来定义FunctorApplicative运算是非常明智的。

相关问题