这只是对 a Traversable
---all Traversable1
s are的稍微严格的定义Traversable
,但反之亦然。对于为什么Traversable
s 需要Applicative
s 的许多(许多)更多细节,也许值得一看Applicative Programming with Effects。从手势上看,如果你只有 a ,那么如果Functor
它包含许多值,就不可能“排序”该函子的效果,因为你的“注入”函数(a -> f b)
是获得b
s 的唯一方法,而你join
的f
.
但是,从广义上讲,当您定义Traversable
s 时,您只需要使用无效果注入函数 ,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 库本身中使用的版本非常相似,并且很有用,因为它们可以利用 regular 背后的库动力[]
,但如果我们定义NonEmpty
稍有不同,我们可以看到有很多Traversable
和之间的平行度Traversable1
。实例可以存在的事实Traversable1
确实是数据类型本身的一个特征——定义基本相同。
import Data.Monoid
import qualified Data.Semigroup as Se
import Data.Traversable
import Data.Foldable
import Data.Semigroup.Foldable
import Data.Semigroup.Traversable
import Data.Functor.Apply
import Control.Applicative
-- For comparison
data List a = Empty | List a (List a)
data NonEmpty a = One a | Many a (NonEmpty a)
instance Functor NonEmpty where
fmap f (One a) = One (f a)
fmap f (Many a as) = Many (f a) (fmap f as)
instance Apply NonEmpty where
(One f) <.> (One a) = One (f a)
(One f) <.> (Many a _) = One (f a)
(Many f _) <.> (One a) = One (f a)
(Many f fs) <.> (Many a as) = Many (f a) (fs <.> as)
instance Applicative NonEmpty where
pure = One
(<*>) = (<.>)
instance Foldable NonEmpty where
foldMap f (One a) = f a
foldMap f (Many a as) = f a <> foldMap f as
instance Foldable1 NonEmpty where
foldMap1 f (One a) = f a
-- Core distinction: we use the Semigroup.<> instead of the Monoid.<>
foldMap1 f (Many a as) = f a Se.<> foldMap1 f as
instance Traversable NonEmpty where
traverse inj (One a) = One <$> inj a
traverse inj (Many a as) = Many <$> inj a <*> traverse inj as
instance Traversable1 NonEmpty where
traverse1 inj (One a) = One <$> inj a
traverse1 inj (Many a as) = Many <$> inj a <.> traverse1 inj as