2

stackage.org 上liftA2<*>针对typeclass存在以下循环声明Applicative

       (<*>) = liftA2 id
liftA2 f x y = f <$> x <*> y

liftA2是可用于站点或站点上的非循环声明<*>。这种完全循环的引用是一种疏忽吗?

更新:

hoogle文档中似乎缺少以下(必要的)澄清声明:

<*> :: Functor F => F (a -> b) -> F a -> F b

并暗示(由于循环声明)

liftA2 :: Functor F => (a -> b -> c) -> F a -> F b -> F c
4

2 回答 2

6

[…] 以下循环定义适用于类型liftA2类。<*>Applicative

       (<*>) = liftA2 id
liftA2 f x y = f <$> x <*> y

liftA2是可用于站点或站点上的非循环定义<*>

确切地说,这些不是方法的定义;它们是默认定义。具有一个参数的类型类只是一组类型,而instance定义是该集合成员资格的入场费。Minimalpragma forApplicative告诉您必须实现这两种方法之一,并且该信息显示在 Haddock 文档中。

liftA2<*>和的实际定义pure特定于 的实例Applicative。一般来说,如果一个类型类包含的方法只能使用该类型类的其他方法来实现,那么该方法并不一定是该类的一部分,因为它可以是一个带有约束的顶级定义。

然而,无论如何都可以包括这样的方法。这可能只是为了方便,因为它更容易根据一个函数定义一个实例,即使它不是“最基本的”操作。这通常也是出于性能原因:当可以比特定类型的默认方法更有效地实现方法时,往往会包含冗余方法。例如,在这种情况下,liftA2可能能够更有效地一起<$>遍历两个结构,而不是使用 遍历一个结构,然后使用 单独遍历另一个结构<*>

GHC还提供DefaultSignatures了一种添加更具体的默认值的方法,通常根据.Genericderiving

这种完全循环的引用是一种疏忽吗?

一点也不,他们是故意的。类型类方法的默认实现中的循环定义非常普遍。例如,Eq在 Haskell 报告中定义如下:

class Eq a where
  (==) :: a -> a -> Bool
  x == y = not (x /= y)

  (/=) :: a -> a -> Bool
  x /= y = not (x == y)

可能会忘记实现其中之一,因此它们都使用默认值,因此代表一个无限循环,但是:

  • 默认情况下,这会生成一个警告(由-Wmissing-methods启用-Wdefault)。

  • 如果Minimal未指定 pragma,则假定类中的所有方法都是必需的。

因此,在这种情况下,实际上唯一的其他选择是从类中删除一个或另一个,或者省略为其中一个提供默认值。如果你想知道这些方法是如何实现的Applicative,要看的是instance具体类型构造函数的实现,比如[], ZipList, Maybe,StateT等等。

于 2021-04-14T21:45:51.757 回答
3

LiftA2 是否有非循环定义?

(<*>)和的实现liftA2特定于类型类的实例Applicative。实际上,对于每个Applicative实例,您需要实现pure, 和(<*>)or liftA2, or 由MINIMALpragma 指定:

class Functor f => Applicative f where
    {-# MINIMAL pure, ((<*>) | liftA2) #-}
    -- …

例如,对于 的Maybe实例Applicative,这实现为:

instance Applicative Maybe where
    pure = Just

    Just f  <*> m       = fmap f m
    Nothing <*> _m      = Nothing

    liftA2 f (Just x) (Just y) = Just (f x y)
    liftA2 _ _ _ = Nothing

    Just _m1 *> m2      = m2
    Nothing  *> _m2     = Nothing

简单地实现pureand(<*>)在这里就足够了,因为 thenliftA2是根据(<*>). 然而,执行其他方法通常更有效。

于 2021-04-14T20:43:57.687 回答