algebra icon indicating copy to clipboard operation
algebra copied to clipboard

Default instances

Open johnynek opened this issue 11 years ago • 5 comments

I think the basics are in place (even well beyond the basics). But one thing we have not totally discussed is default instances.

Algebird, unlike Spire, gives default instances for almost everything. People are almost never surprised, but it does mean that Semigroup[Int] is addition (not max, or min or multiplication, or xor or and). I know you guys disagree about that choice so let's set that aside.

What about Semigroup[(A, B)]. To me, by default that should be combine on A and B in a tuple.

What about Eq[(A, B)], List[A], Map[K, V]? Again, those seem unambiguous to me.

Without these, users immediately start writing combinators to do anything of use.

Suppose we do add these. How do we implement them? In algebird we use code gen at compile time (in some cases, some are still the old way) to generate 22 tuple instances. A modern way might be to use macros. That has issues with the separate compilation requirements. I lean towards code-gen for these things.

I'd like to see code-gen versions for Eq, PartialOrder, Order for Tuples. I'd like to see default instances of those for sequences and primitives as well.

How does this sound?

johnynek avatar Jan 16 '15 18:01 johnynek

I'm actually not against having a default of addition for Semigroup and friends. I am a little uneasy about adding the defaults into the companion object. We started with this model and moved away because we started hitting a lot ambiguous implicits.

One of the issues is that implicits in _type class_ companion objects do not mesh well with complex inheritance hierarchies, like we have in algebra. Scala only checks the companion object of the type you asked for. So, if you wanted a Semigroup[A], then it wouldn't search Monoid[A] for an instance. This meant we started making implicits like implicit def MonoidIsSemigroup[A](implicit A: Monoid[A]): Semigroup[A] = A. This ended up with weird recursive/divergent lookups that were hard to fix. For example, I want a monoid for Option[A] - ok that exists if we have a Semigroup for A. Well, we have a Semigroup for A if we have a Monoid for A. Rinse and repeat.

These kinds of issues were what prompted me to go with an import approach, since if there was an implicit Monoid[A], the Semigroup[A] would be available naturally. It is definitely annoying to have to remember to add an import, but it also meant I spent less time debugging diverging and ambiguous implicits.

My initial impression of algebra.std was that it would provide instances for most types in the standard library (this also implies an extra import to get them). On the other hand, algebra itself would be focused on just providing the type classes. It seems like std is currently more about providing testable instances for laws though and isn't really meant to be used otherwise. So, I'm still not sure of std's fate.

Spire uses codegen for tuples too - covering eq, order, and basic algebras. CodeGen is a bit messy, but is pretty reliable, so I'd vote for codegen too.

tixxit avatar Jan 16 '15 18:01 tixxit

One alternative is to simply duplicate all the implicit instances for all parent type classes, so we don't have to have the implicit-child search I described above. The only issue is that this isn't very extensible. If someone adds a new type class outside of algebra that extends Semigroup, for instance, then any implicits they add to the type class's companion object would not be available as Semigroups without an extra import, since implicits cannot be retroactively added to Semigroups companion object.

tixxit avatar Jan 16 '15 18:01 tixxit

What about the Preferred[A, B] approach we discussed before. I wonder if that can disambiguate these issues for us?

I guess imports are not too bad, I just hate users having import a ton of stuff as boiler plate.

johnynek avatar Jan 16 '15 19:01 johnynek

I guess one alternative which doesn't require extra imports is to put the implicits on a package object? Not sure how I feel about that (I have the sense people consider that in poor taste, somehow), but it's an option.

avibryant avatar Jan 17 '15 03:01 avibryant

I'm not sure it totally resolves this issue, but in the short term I plan to add CommutativeGroup instances for most of the number types (using addition).

I think Spire proved you didn't need to do this (since usually you can just use additive instances). But since Algebird users, Scalaz users, etc expect these (and the world hasn't ended) maybe it's worth providing them?

(And assuming we stick with imports, Spire is free to ignore these instances anyway.)

non avatar Feb 09 '15 16:02 non