2
data MyData a b = MyData a b b

Why is the first instantiation good and the second not ?

instance Foldable (MyData a) where
    foldMap f (MyData x y z) = f y <> f z
instance Foldable (MyData a) where 
       foldMap f (MyData x y z) = f z

f maps both y and z into a monoid, so f z and f y <> f z are instances of that monoid. So, why is the second not ok ?

Johnny
  • 447
  • 2
  • 8
  • 2
    Both are OK, IMO, but the second one neglects `y :: b`, which is a bit strange. `Foldable` is roughly the class of type constructors that allow conversion into a list. It's up to you to choose what to include in that list. You can even have `foldMap _ _ = mempty`. Or `... = f z <> f z`. These are not natural choices, though. – chi Jan 18 '22 at 13:18
  • 1
    A hypothetical justified usecase for skipping an argument would be to store some kind of referential value that does not necessarily participate in the "collection" part – radrow Jan 18 '22 at 13:25
  • 1
    A non-hypothetical justified use case for skipping (or duplicating) an argument would be a container that stored multiplicities and for which there could be a zero multiplicity, a la [`MultiSet`](https://hackage.haskell.org/package/multiset-0.3.4.3/docs/Data-MultiSet.html). I think that particular implementation tries to prune away values with zero multiplicity, but that's mostly a performance optimization. – Daniel Wagner Jan 18 '22 at 15:10

1 Answers1

2

Your instance is alright as it does not violate any of the Foldable laws, as long as you keep it consequent. It is just odd that MyData holds two components of type b, one of which is not considered in folds.

radrow
  • 6,419
  • 4
  • 26
  • 53
  • It *seems* like there should be a violation, but yeah, I'm not sure what it would be yet. – chepner Jan 18 '22 at 13:25
  • I had a short dilemma with the functor one, but it maps both sides to the same thing so... – radrow Jan 18 '22 at 13:26
  • Probably whatever is bothering me involves a side effect, so it's not a "real" problem. – chepner Jan 18 '22 at 13:26
  • 2
    @chepner Indeed: this is an acceptable `Foldable` instance; it just doesn't give rise to a lawful `Traversable`, as the traversable laws would require each `b` to be used exactly once. – duplode Jan 18 '22 at 13:42
  • @duplode Yeah, I think that captures my concern. (I remember reading somewhere about `Traversable` being better than `Foldable` because it had laws or better laws.) – chepner Jan 18 '22 at 14:37
  • @chepner As I understand it, `Foldable` was separated from `Traversable` mainly for the sake of giving instances to things like non-`Functor` sets. Still, I don't really think there's anything wrong with `Foldable`. Here is how I'd give a positive spin to the distinction: writing a `Foldable` instance amounts to choosing a path for walking through a structure, and for each choice there will be *at most* one compatible `traverse` that is lawful. [This answer of mine](https://stackoverflow.com/a/61199603) is an extended look at `Traversable` from this point of view. – duplode Jan 18 '22 at 15:41
  • @duplode Here we go: https://twitter.com/GabriellaG439/status/1442869204659539970?s=20. Not so much that Foldable is *bad*, but (my interpretation) knowing that a data structure is `Foldable` isn't enough to know how its instance works, because there's too much flexibility in how it *can* work. – chepner Jan 18 '22 at 15:52
  • @chepner While I sympathise with the general point, in this specific case I feel that "this structure has contents that can be extracted in a favoured order" is (barely) enough to justify the abstraction, on a conceptual level. Also, I don't think lawlessness has much to do with the "wat" objections to `Foldable`. In particular, `(,) a` and other such controversial instances are all `Traversable` as well, which is enough to uniquely determine their `Foldable` implementation. – duplode Jan 18 '22 at 17:15