在semigroupoids包中,我找到了以下定义:
class (Foldable1 t, Traversable t) => Traversable1 t where
traverse1 :: Apply f => (a -> f b) -> t a -> f (t b)
sequence1 :: Apply f => t (f b) -> f (t b)
sequence1 = traverse1 id
traverse1 f = sequence1 . fmap f
为什么上下文边界设置为Apply
(Applicative
没有pure
)而不是Functor
?显然你需要覆盖其中一个定义,所以“仅仅”Functor
这是不可能的吗?
答案 0 :(得分:6)
这只是Traversable
的一个略微收紧的定义---所有Traversable1
都是Traversable
,但反之亦然。关于Traversable
需要Applicative
的原因的许多(很多)详细信息,或许值得一看Applicative Programming with Effects。从根本上说,如果你只有一个Functor
,那么如果它包含很多值就不可能“排序”该仿函数的效果,因为你的“注入”函数(a -> f b)
是获得{的唯一方法{1}}并且您不能b
join
的图层。
但是,广泛地说,当您定义f
时,您只需要使用无效注入函数Traversable
来表示“默认”值,这正是pure
消除的。这就是为什么Traversable1
是一个实例但NonEmpty
不是。
为了使事情具体化,请考虑身份仿函数的这些示例实例,[]
,Maybe
列表和常规NonEmpty
。
[]
我们这里只需要一个newtype Id a = Id a
instance Functor Id where fmap f (Id a) = Id (f a)
instance Applicative Id where
pure = Id
(Id f) <*> (Id x) = Id (f x)
个实例,因为Functor
只有一个元素而没有“默认”分支 - 它非常简单。
Id
我们需要instance Traversable Id where traverse inj (Id a) = Id <$> inj a
instance Traversable1 Id where traverse1 inj (Id a) = Id <$> inj a
pure
Nothing
Maybe
的情况(仅比Id
稍微复杂一点)。
instance Traversable Maybe where
traverse _ Nothing = pure Nothing
traverse inj (Just a) = Just <$> inj a
instance Traversable1 Maybe
不能存在,因为Maybe
有默认分支;我们看到了这一点,因为如果我们只有pure
约束,我们就无法使用Apply
。
data NonEmpty a = NonEmpty a [a]
instance Functor NonEmpty where fmap f (NonEmpty a as) = NonEmpty (f a) (fmap f as)
instance Apply NonEmpty where
(NonEmpty f fs) <.> (NonEmpty x xs) = NonEmpty (f x) (fs <*> xs)
instance Pointed NonEmpty where
point a = NonEmpty a []
instance Applicative NonEmpty where
(<*>) = (<.>)
pure = point
instance Traversable NonEmpty where
traverse inj (NonEmpty a as) = NonEmpty <$> inj a <*> (traverse inj a as)
由于我们只使用了(<*>)
而非pure
,因此我们可以将其设为Traversable1
个实例
instance Traversable1 NonEmpty where
traverse1 inj (NonEmpty a []) = (`NonEmpty` []) <$> inj a
traverse1 inj (NonEmpty a (b: bs)) =
(\a' (NonEmpty b' bs') -> NonEmpty a' (b': bs'))
<$> inj a
<.> traverse1 inj (NonEmpty b bs)
但这对[]
不起作用,因为我们最终使用pure
作为“默认”分支
instance Traversable [] where
traverse _ [] = pure []
traverse inj (x:xs) = (:) <$> inj x <*> traverse inj xs
编辑:最初我根据我对Traversable1 NonEmpty
的定义玩得很快。目前的版本确实有效,但眼睛更难。之前我尝试了traversing
内部列表,它在精神上有效,因为[]
的第二个广告位中的NonEmpty
有第一个帮助它的插槽,但这不能直接使用,因为内部列表有一个空案例[]
,需要pure
。相反,我们必须通过在第一个位置“窃取”始终存在的a
然后在遍历之后替换它来避免该空案例。
该方法(和数据类型定义)与Semigroups和Semigroupoids库本身使用的版本非常相似,并且非常有用,因为它们可以利用常规[]
后面的库动量,但是如果我们定义{{1有点不同,我们可以看到NonEmpty
和Traversable
之间存在大量的并行性。 Traversable1
实例可以存在的事实确实只是数据类型的一个特征 - 定义基本相同。
Traversable1