I remember reading somewhere that a type like this one can't be Traversable:
data Bar a = Bar a deriving(Show)
instance Functor Bar where
fmap f (Bar x) = Bar (f x)
instance Foldable Bar where
foldMap f (Bar x) = f x <> f x
The bit of the explanation I remember is that for foldMap = foldMapDefault to hold, the Traversable instance would have to visit its elements more than once, which a lawful instance can't do. However, I don't remember why a lawful instance can't do that. Consider this one:
instance Traversable Bar where
sequenceA (Bar x) = Bar <$ x <*> x
It looks fine at first glance. What's unlawful about doing that?
There are a few reasonable vantage points from which to tackle this problem. My strategy here, while perhaps a little unpolished, does the job just fine, while illustrating the key ideas without too many technical complications.
This answer has two parts. The first part, which can be read independently if the reader is short of time, presents the chosen perspective and the main conclusion. The second part expands on that by providing detailed justification. At its very end, there is a concise list of things allowed and forbidden by the
Traversablelaws.The answer grew a little long, so here is a list of section headings for skipping around with Ctrl+F:
Part one
Part two
One might, in fact, object that this answer is too long for this format. In my defense, I note that the parent question is addressed in the sections about duplicating effects, and everything else either justifies the direct answer or is relevant in context.
Shape and contents
Ultimately, it all comes down to what I like to call the shape-and-contents decomposition. In the simplest possible terms, it means
Traversablecan be encoded through a class like this:filltakes atfunctorial shape, which we represent here using at ()value, and fills it with contents drawn from an[a]list. We can rely onFunctorandFoldableto give us a conversion in the other direction:With
fillanddetach, we can then implementsequenceAin terms of the concretesequenceAfor lists: detach, sequence the list, and then fill:It is also possible, if a little awkward, to define
fillin terms ofTraversable:(For prior art on this approach, see, for instance, this answer.)
In terms of
Fillable, theTraversablelaws say thatfillanddetachalmost witness the two directions of an isomorphism:fillmust be a left inverse ofdetach:This amounts to the identity law of
Traversable.detachmust behave as a left inverse offillas long asfillis only supplied lists and shapes with compatible sizes (otherwise the situation is hopeless):This property corresponds to the composition law. On its own, it is, in fact, stronger than the composition law. If we assume the identity law, however, it becomes materially equivalent to the composition law. That being so, it is fine to take these properties as an alternate presentation of the
Traversablelaws, except perhaps if you want to study the composition law in isolation. (There will be a more detailed explanation of this near-equivalence in the second part of the answer, after we look more closely at the composition law.)Duplicating effects
What has all of that to do with your question? Suppose we want to define a traversal that duplicates effects without changing the traversable shape (as changing it would be a flagrant violation of the identity law). Now, assuming that our
sequenceAis actuallysequenceFill, as defined above, which options do we have? SincesequenceFillpiggybacks onsequenceList, which is known to visit each element exactly once, our only hope is to rely on a companionFoldableinstance such thattoList, and thereforedetach, generates a list with duplicated elements. Can we make theFillablelaws hold in such a scenario?The first law is not a big problem. In principle, we can always define
fillso that it undoes the duplication, discarding the extra copies of elements introduced bydetach.If we have a deduplicating
fill, however, the second law is a lost cause. By parametricity,fillcan't distinguish between a list with duplicates introduced bydetachfrom any other list we might feed it, and sodetach . uncurry fillwill always replace some elements with duplicates of other ones.That being so, a
traverseFillthat duplicates effects can only arise from an unlawfulFillable. Since theFillablelaws are equivalent to theTraversableones, we conclude that a lawfulTraversablecannot duplicate effects.(The effect duplication scenario discussed above, by the way, applies to your
Bartype: it fails the secondFillablelaw, and therefore it also fails theTraversablecomposition law, as your counterexample shows.)A paper which I really like that covers this question and matters adjacent to it is Bird et al., Understanding Idiomatic Traversals Backwards and Forwards (2013). Though it might not look like that at first, its approach is closely related to what I have shown here. In particular, its "representation theorem" is essentially the same as the
detach/fillrelationship explored here, the main difference being that the definitions in the paper are tighter, obviating the need to fuss about whatfillis supposed to do when given a list of the wrong length.The free applicative presentation
Though I won't attempt to present the full argument of the Bird et al. paper, in the context of this answer it is worth noting how its proof of the aforementioned representation theorem relies on a formulation of the free applicative functor. We can twist that idea a little to obtain an additional presentation of
Traversablein terms ofApfrom free'sControl.Applicative.Free:I'm pretty much sure the following are appropriate laws, though it might be worth it to doublecheck:
Though this looks far removed from the humble
detachandfillcombination we started with, it ultimately is just a more precise encoding of the same idea. AnAp f (t a)value is either a singlet astructure wrapped inPure, or a sequence of zero or moref avalues (theApconstructor) capped by a function of the appropriate arity which takes as manyas as there aref as and produces at a. In terms of our initial stab at the shape-and-contents decomposition, we have:The
f as in theApvalues correspond to the list of contents;The function (if there is one) encodes which shape to use when reassembling the traversable structure, as well as how it should be filled. The shape-list mismatch problem is neatly avoided at type level, it being statically guaranteed that the function will have the right arity;
As for the effects,
retractApperforms the role of combining them in the obvious way, much likesequenceListdid insequenceFill.(End of part one.)
Fillable and Traversable, up close
As promised, part two will start with proving that
Fillablereally is a presentation ofTraversable. In what follows, I will use tweaked versions of the definitions which are easier to manipulate with pen and paper:(By the way, the cleaner definitions above provide a good occasion to note that, if we were to leave the confines of writing actual Haskell, it wouldn't take much to move the shape carried all along the way in
sequenceFill'to type level, in effect partitioning the traversable functor according to the possible shapes. As far as I understand it, that would get us well on the way towards the standard dependently typed treatment of containers. I won't delve further into that here; if you feel like exploring, I heartily recommend the answers on the topic by Conor McBride (pigworker).)Identity
We can begin by dealing with the identity law, which is a more straightforward matter:
Since all steps in the derivation above are reversible, we can conclude the detach-then-fill direction of the isomorphism is equivalent to the identity law.
Composition
As for the composition law, we might start by using the same strategy:
At this point, it appears we are stuck with a property weaker than the
detach' . fill' = idwe expected to uncover. On the upside, there are a few nice things about it:All steps in the derivation above are reversible, so the property is equivalent to the composition law.
The
sequenceList' . detach'andfmap (fmap fill' . sequenceList')extra terms that pad both sides of the equation make it so that everyfill'is preceded by adetach', and everydetach'is followed by afill'. That means the precondition of the fill-then-detach law automatically holds.The fill-then-detach law is strictly stronger than this property. That being so, if
detach' . fill' = id(within the bounds of the precondition, etc.) then this property, and therefore the composition law, also holds.I will get back to these observations in a little while in order to justify my earlier claim that
detach' . fill' = idcan be regarded as aTraversablelaw.Idempotency
A short break, before we carry on with our regular schedule. There is a little piece of trivia we can uncover by specialising both applicative functors in the composition law to
Identity. Continuing from where we stopped:We end up with an idempotency property for
fill' . detach', and, indirectly, also forsequenceA. Though such a property is unsurprising as far asTraversableis concerned, as it follows immediately from the identity law, it is rather interesting that it also follows from the composition law on its own. (On a related note, I sometimes wonder if we could get any mileage out of aSemitraversableclass of sorts, which would only have the composition law.)Duplicating effects: once more, with feeling
Now it is a good time to revisit your original question: exactly why duplication of effects causes trouble with the laws? The
Fillablepresentation helps to clarify the connection. Let's have another look at both sides of the composition law, in the form we have just given it:Let's assume the identity law holds. In that case, the only possible source of duplicated effects in
sequenceFill'are elements being duplicated bydetach'(assequenceList'doesn't duplicate, andfill'can't duplicate because of the identity law).Now, if
detach'introduces duplicates at certain positions,fill'must remove them so that the identity law holds. Thanks to parametricity, however, elements at those positions will be always removed, even if the relevant elements aren't actually duplicated because the list wasn't created bydetach'. To put it in another way, there is a precondition forfill'being an innocuous removal of duplicates, namely, that it must be given lists that might have been produced bydetach'. In the composition law, it might happen, depending on what the applicative effect is, that the firstsequenceList'produces lists that fall outside of this precondition. In that case, thefmap fill'that follows it on the right hand side will eliminate inner effects (keep in mind the firstsequenceList'only deals with the outer applicative layer) that weren't actually duplicated, the difference will be duly detected by the secondsequenceList' . detach', which acts on the inner effect layer, and we'll end up with a law violation.In fact, we can affirm something stronger: if
sequenceFill'duplicates effects, it is always possible to violate the law in the manner described above. All we need for such a claim is a good enough counterexample:The trick is that if you sequence a list that only contains copies of
advance, the list you'll be given back is guaranteed not to have any duplicatedConst (Sum Natural)effects:That being so, if such a list reaches a
sequenceFill'implementation that duplicates effects, thefmap fill'in it will invariably discard non-duplicates:A violation is now inevitable:
(
advance, as you might have noted, is very similar to the counterexample in your answer, only tweaked so that it can be used with arbitrary traversable-like structures.)This suffices to show that duplication of effects is incompatible with the composition law.
Simplifying the composition law
At this point, there is a convenient way to justify why we can use the simplified fill-then-detach property...
... in lieu of the bulkier composition law we have been dealing with in the last few sections. Again, assume the identity law holds. In that case, we can classify the possible implementations of
detach'in two cases:detach'never duplicates elements. As a consequence,detach'is, within the limits of the fill-then-detach precondition, surjective (for instance, if the traversable functor is a vector of length six,detach'can generate all possible lists of length six, though it won't generate lists with other lengths). If a function that has a left inverse is surjective, though, its left inverse is also a right inverse. Therefore,detach' . fill' = idwithin the bounds of the precondition, and the composition law holds.(The "within the limits of the fill-then-detach precondition" bit might feel like handwaving, but I believe it can be made rigorous by using dependent types to partition the traversable functor type according to the shapes, in the way I alluded at the beginning of the second part.)
detach'duplicates elements. In that case, though, the ensuing duplication of effects means the composition law won't hold, as we have just shown, and neither will the strongerdetach' . fill' = idproperty.That being so, the the
Traversablecomposition law and theFillablefill-then-detach law always agree as long as the identity law holds; the difference between them can only show up in implementations that violate the identity law. Therefore, if taken together, theFillablelaws as stated in the first part of the answer are equivalent to theTraversableones.foldMapDefault and the other naturality law
A beautiful feature of the
Fillablepresentation is how it makes it explicit that the only free choice we have in defining a lawfulsequenceAis that of the order in which the effects will be sequenced. Once a certain order is chosen by picking aFoldableimplementation, which determinestoListanddetach',sequenceList'must follow that order upon sequencing the effects. Furthermore, sincefill'is (within the bounds of the fill-then-detach precondition) a full inverse ofdetach', it is uniquely determined.The class hierarchy we have in the base libraries is not arranged in quite the same way as
Fillable: the realsequenceAis a self-sufficient method ofTraversablewhich, unlikesequenceFill', does not rely onFoldablefor its implementation. Rather, the connection betweenFoldableandTraversableis straightened out by a superclass coherence law:(There is an analogous property for
FunctorandfmapDefault, but parametricity means it follows from the identity law.)In terms of
toListandsequenceA, this law becomes:If we use
sequenceA = sequenceFill'to bring us back to theFillablepresentation...... we conclude that the
foldMapDefaultlaw holds automatically.Bird et al.'s "'naturality' in the datatype"
After the identity and composition laws, the third best known law of
Traversableis naturality in the applicative functor, often referred to simply as the naturality law:While useful, as well as significant theory-wise (it reflects an alternative view of
sequenceAas a natural transformation in the category of applicative functors and applicative homomorphisms, discussed for instance in Jaskelioff and Rypacek, An Investigation of the Laws of Traversals), the naturality law follows from a free theorem forsequenceA(in the vein of Voigtländer, Free Theorems Involving Constructor Classes), and so there isn't much to say about it in the context of this answer.The Bird et al. paper mentioned in the first part discusses, in section 6, a different naturality property, which the authors call "'naturality' in the datatype". Unlike the better known naturality law, it is a naturality property for the traversable functor itself:
(Bird et al. don't use
Foldableexplictly, rather stating the property in terms ofcontents = getConst . traverse (Const . (:[]). Assuming thefoldMapDefaultcoherence law holds, there is no difference.)The
Fillableperspective suits this naturality property very nicely. We can begin by noting we can lift a natural transformation on some functortto work onDecomp tas well:If
rpreservestoList(or, we might even say, if it is a foldable homomorphism), it follows that it also preservesdetach', and vice-versa:(
hmapDecompdoesn't affect the list of contents, and, being a natural transformation,rcommutes with the(() <$)half ofdetach'.)If we further assume the
Fillablelaws, we can use the fact thatfill'anddetach'are inverses (within the bounds of the precondition of the fill-then-detach law) to shiftrfromdetach'tofill':That is, applying
rto the shape and then filling it is the same as filling and then applyingrto the filled shape.At this point, we can work our way back to
sequenceFill':We have thus obtained the naturality in the traversable functor property, as might have been expected given the equivalence between the
FillableandTraversablelaws. Still, we did learn something in the process. Bird et al. were justified in being cautious with the word "naturality" when talking of this property, as the restriction totoList-preserving natural transformations seems extraneous in the context of the standard class hierarchy. From theFillableperspective, though,fill'is determined by our choice ofFoldableinstance, and so the property is about as sharp as any other naturality property for a constructor class. That being so, I believe we can drop the scare quotes around "naturality".Executive summary: dos and don'ts of Traversable
We are now in a position to make a fairly complete list of the consequences of the
Traversablelaws. Though there is no real difference, I will speak here in terms oftraverse, as using it instead ofsequenceAmakes it a little clearer what is meant by "elements", in contrast with "effects".A lawful
traversemust not:Change the traversable shape in any way, due to the identity law.
Drop or duplicate elements, due to the identity law.
Reorder elements in the traversable structure, due to the identity law.
Duplicate effects, even if there is no duplication of elements, due to the composition law.
A lawful
traversemay:A lawful
traversemust:toListfrom theFoldableinstance for the type, due to thefoldMapDefaultlaw.A lawful
traversewill:Preserve applicative homomorphisms, that is, natural transformations that preserve
pureandreturn, due to the naturality law, which holds freely.Preserve foldable homomorphisms, that is, natural transformations that preserve
toList/foldMap, due to the naturality-in-the-traversable law, which follows from the identity and composition laws.