Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write length function for all Monoids

Tags:

haskell

I'm playing around with rewriting simple functions in different ways and I clearly misunderstand some core concepts. Is there a better way to work with limited types like these?

mlength :: Monoid m => m -> Int
mlength mempty   = 0
mlength (l <> r) = mlength l + mlength r

It fails compilation with the following error:

Parse error in pattern: l <> r

I can see that my usage of <> is misguided because there are multiple correct matches for l and r. Even though it looks like it doesn't matter which value is assigned, a value still has to be assigned in the end. Maybe there's a way for me to assert this decision for specific Monoid instances?

"ab" == ""   <> "ab" 
"ab" == "a"  <> "b" 
"ab" == "ab" <> ""
like image 930
codenoodle Avatar asked Feb 15 '20 06:02

codenoodle


2 Answers

A monoid, in the general case, has no notion of length. Take for instance Sum Int, which is Int equipped with addition for its monoidal operation. We have

Sum 3 <> Sum 4 = Sum 7 = Sum (-100) <> Sum 7 <> Sum (100)

What should be its "length"? There is no real notion of length here, since the underlying type is Int, which is not a list-like type.

Another example: Endo Int which is Int -> Int equipped with composition. E.g.

Endo (\x -> x+1) <> Endo (\x -> x*2) = Endo (\x -> 2*x+1)

Again, no meaningful "length" can be defined here.

You can browse Data.Monoid and see other examples where there is no notion of "length".

Const a is also a (boring) monoid with no length.

Now, it is true that lists [a] form a monoid (the free monoid over a), and length can indeed be defined there. Still, this is only a particular case, which does not generalize.

like image 143
chi Avatar answered Oct 10 '22 08:10

chi


The Semigroup and Monoid interfaces provide a means to build up values, (<>). They don't, however, give us a way to break down or otherwise extract information from values. That being so, a length generalised beyond some specific type requires a different abstraction.

As discussed in the comments to chi's answer, while Data.Foldable offers a generalised length :: Foldable t => t a -> Int, it isn't quite what you were aiming at -- in particular, the connection between Foldable and Monoid is that foldable structures can be converted to lists/the free monoid, and not that foldables themselves are necessarily monoids.

One other possibility, which is somewhat obscure but closer to the spirit of your question, is the Factorial class from the monoid-subclasses package, a subclass of Semigroup. It is built around factors :: Factorial m => m -> [m], which splits a value into irreducible factors, undoing what sconcat or mconcat do. A generalised length :: Factorial m => m -> Int can then be defined as the length of the list of factors. In any case, note that we still end up needing a further abstraction on the top of Semigroup/Monoid.

like image 5
duplode Avatar answered Oct 10 '22 09:10

duplode