Suppose I have some type with an associative binary operation that feels a lot like append
except that the operation may fail. For example, here's a wrapper for List[Int]
that only allows us to "add" lists with the same length:
case class Foo(xs: List[Int]) {
def append(other: Foo): Option[Foo] =
if (xs.size != other.xs.size) None else Some(
Foo(xs.zip(other.xs).map { case (a, b) => a + b })
)
}
This is a toy example, but one of the things it has in common with my real use case is that we could in principle use the type system to make the operation total—in this case by tracking the length of the lists with something like Shapeless's Sized
, so that adding lists of unequal lengths would be a compile-time error instead of a runtime failure. That's not too bad, but in my real use case managing the constraints in the type system would require a lot more work and isn't really practical.
(In my use case I have a sensible identity, unlike in this toy example, but we can ignore that for now.)
Is there some principled way to do this kind of thing? Searching for a -> a -> m a
or a -> a -> Maybe a
on Hoogle doesn't turn up anything interesting. I know I can write an ad-hoc append
method that just returns its result wrapped in whatever type I'm using to model failure, but it'd be nice to have something more general that would give me foldMap1
, etc. for free—especially since this isn't the first time I've found myself wanting this kind of thing.