4

I realise that the answer may be that there are multiple valid such instances (as is the case for e.g. integers; sum, product, ...). Perhaps someone has a more satisfying answer than this?

As Joachim Breitner excellently explains in this answer How do you implement monoid interface for this tree in haskell? any applicative has a monoid instance:

mempty :: Applicative f => Monoid a => f a
mempty = pure mempty

mappend :: Applicative f => Monoid a => f a -> f a -> f a
mappend f g = mappend <$> f <*> g

So I was wondering why Data.Tree.Tree from containers does not have such an instance? Same argument could be used for any other monad without an accompanying monoid instance. It only seems natural to me that they should have such instances. Maybe this is not the case. I hope someone can enlighten me.

I suppose another reason could be that the instance I propose for trees is not "useful". This is as unsatisfying as the multiple valid instances argument in my opinion.

fredefox
  • 681
  • 3
  • 11
  • "Non-empty, possibly infinite, multi-way trees; also known as rose trees." - I don't think mempty can exist, could be wrong though. – Caramiriel Sep 24 '19 at 14:53
  • A tree is an instance of `Foldable`, so I guess that might give some tooling to work with threes that have values that belong to a type that is an instance of `Monoid`. But I agree it might make sense to make a tree an instance of `Monoid` as well. – Willem Van Onsem Sep 24 '19 at 14:53
  • @Caramiriel: if the item it wraps is a `Monoid` as well, we could define it as `mempty = Node mempty []` – Willem Van Onsem Sep 24 '19 at 14:54
  • 2
    your `mappend` definition is equivalent to `liftA2 mappend`, just like [`Ap`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Monoid.html#t:Ap) [defines](https://hackage.haskell.org/package/base-4.12.0.0/docs/src/Data.Monoid.html#line-189). – Will Ness Sep 24 '19 at 16:23
  • @Caramiriel, as @Willem Van Onsem mentions the instance that Joachim Breitner proposes does indeed require a monoid instance for nodes of the tree so we can have `mempty = pure mempty = Node mempty` – fredefox Sep 25 '19 at 08:20
  • Good point @WillNess. Though I personally prefer to write it like `mappend <$> f <*> g`. It generalizes to any arity: `User <$> parseName <*> parseEmail <*> parsePassword` etc. ad nauseam. – fredefox Sep 26 '19 at 18:59
  • 1
    @fredefox, `liftA2` composes more cleanly. `liftA2 . liftA2 . liftA2` expresses the idea of lifting under multiple functors. – dfeuer Sep 26 '19 at 19:39

2 Answers2

8

I don't know why it isn't available. However, the instance you propose is available once and for all via the Ap newtype, which provides an instance (Applicative f, Monoid m) => Monoid (Ap f m). So if you need the instance you write, you can get it with this, even though it doesn't exist on the bare Tree type.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • Ah I see. `Ap` "witness" the observation that Joachim Breitner mentioned. I didn't know about this type. I wonder, then, when something qualifies as a "natural" monoid instance as e.g. for `[]` with `mappend = (++)` and when something is "naturally ambiguous" for which we employ `Sum`, `Product`, `Ap` et. al. from `Data.Monoid`. Certainly useful to look closer into `Data.Monoid` though. Thanks for the tip! – fredefox Sep 25 '19 at 08:24
  • @fredefox I think it's fundamentally subjective. The standard I would use is "if I told you this type had a monoid instance but didn't tell you how it worked, would you immediately know what the instance probably does?". But that question has very different answers for people who are only passingly familiar with a type/library than for people who use it substantially. Ultimately we're just stuck with the judgement of whoever wrote the library, but can usually work around a decision we disagree with via newtypes, so I don't think anyone loses much sleep over it. – Ben Sep 26 '19 at 00:35
  • 1
    Unless you're a stickler like me I guess ;) but in all seriousness I think that's a very nice observation. A design principle could be that the instance should be immediately obvious (in the case of library design) or driven by the use-case (in the case of application code). I suppose it's just a fact of life that type-classes and ditto-coherence is a bit at odds with the mathematical concepts that they represent. What I mean is that it's really the gadget `(Nat, (+), 0, ...laws)` that *is* a monoid and like-wise `(Nat, (*), 1, ...laws)` and not - as we like to pretend - `Nat` itself. – fredefox Sep 26 '19 at 18:51
1

There are multiple valid instances. Tree also supports a "zippy" Applicative with its corresponding Ap-based monoid:

instance Applicative Tree where
  pure a = let t = Node a (repeat t) in t
  liftA2 f (Node a as) (Node b bs) =
    Node (f a b) (zipWith (liftA2 f) as bs)

instance Semigroup a => Semigroup (Tree a) where
  Node a as <> Node b bs =
    Node (a <> b) (zipWith (<>) as bs)

instance Monoid a => Monoid (Tree a) where
  mempty = Node mempty (repeat mempty)

I have no idea what instance, if any, would actually be useful.


Just for fun, here's a slightly silly one that just lifts the constructor over underlying monoids:

instance Semigroup a => Semigroup (Tree a) where
  Node a as <> Node b bs = Node (a <> b) (as ++ bs)

instance Monoid a => Monoid (Tree a) where
  mempty = Node mempty []
dfeuer
  • 48,079
  • 5
  • 63
  • 167