我试图了解Applicative
界面究竟需要什么才能执行任何traverse
。我被卡住了,因为他们没有在默认实现中使用,好像约束是严格的。 Haskell的类型系统是否太弱而无法描述实际需求?
-- | Map each element of a structure to an action, evaluate these actions
-- from left to right, and collect the results. For a version that ignores
-- the results see 'Data.Foldable.traverse_'.
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
-- | Evaluate each action in the structure from left to right, and
-- and collect the results. For a version that ignores the results
-- see 'Data.Foldable.sequenceA_'.
sequenceA :: Applicative f => t (f a) -> f (t a)
sequenceA = traverse id
一个可能相关的问题,为什么sequenceA_
中定义了Foldable
?
答案 0 :(得分:3)
traverse
和sequenceA
都需要处理Traversable
为空时发生的情况。然后,您在Applicative
上下文中没有任何元素可以用来突出其他内容,因此您需要pure
。
您提出的定义有点误导,因为正如您所指出的那样,它们是相互依赖的。当你真正实现其中一个时,你会遇到空集合问题。并且您会遇到<*>
的需要,因为Functor
无法为某些仿函数f a
汇总f
的不同值。
因此Applicative
约束存在,因为对于大多数类型,为了实现traverse
或sequenceA
,您需要Applicative
提供的工具。
据说有些类型您不需要pure
或不需要<*>
。如果您的收藏品永远不会是空的,那么您就不需要pure
,例如NonEmpty
。如果您的收藏品从不包含多个元素,则您不需要<*>
,例如Maybe
。有时候你也不需要,只有fmap
才可以逃脱,例如一个元组部分,例如(a,)
)。
Haskell可以有一个更细粒度的类型层次结构,将Applicative
分解为更细粒度的部分,使用pure
和<*>
的单独类,这样可以让你做出不同的约束较弱的Traversable
版本(例如TraversableNonEmpty
和TraversableSingleton
)。据推测,在Haskell中对此的需求还不够大。
请注意,其他生态系统已选择具有更细粒度的类型层次结构(请参阅Scala的cats
或scalaz
库)。我个人觉得这种区别偶尔会有用,但不是绝对有用。
关于你的第二个问题,如果你知道该怎么做就是拆掉某些东西,你仍然可以沿途执行效果,但你不一定能恢复原来的结构。因此sequenceA_
为Foldable
的原因。它的强度绝对低于sequenceA
。