-
Notifications
You must be signed in to change notification settings - Fork 69
Integrating cats.kernel.Semigroup and friends #218
Comments
I think we can look at merging algebra into cats now that cats has reached 1.0 algebra has been stable for quite some time, and the main mission was a common set of classes between algebird and spire. I think a cats-algebra module would make a lot of sense and core could eventually depend on it. |
I would very much like to see that as well: typelevel/cats#2041 |
Regarding the default monoid/group: the meaning really depends. Say you have a However, if you use permutation matrices to represent permutation group elements, you want the multiplicative group notation. (And you'll want to avoid Regarding |
I completely agree with this notion. I'd just like to see consistency in the ecosystem.
Instead of using
Should we just add those ring structures (along with |
I couldn’t see almost any code that we wanted that is generic on the Field stuff and Field also has the ugly aspect that inverse/div is not total (it can throw). I think the marginal win of the Field/DivisionRing stuff isn’t worth including (also I don’t know of any interesting instances other than numbers). |
This makes me uncomfortable as well. If scala had predicate/refinement types we could define it as trait DivisionRing[A] {
def div(x: A)(y: {A => y != zero}): A
} but alas we do not, maybe in Scala 4. 😄 @johnynek what do you suggest we do about |
Yes. That’s precisely what I support. Spire is great and not going anywhere. If you are doing interesting numerical work, you should use it. It is fine if they keep the Field related classes. |
Would be great to stop the hierarchy in algebra at CommutativeRing, so that we avoid the duplicate But I'm very adamant against merging |
Note: the precision of the commutative ring tower starts to be important when dealing with polynomials in a generic way. But the polynomial code in Spire is still a bit shaky. |
How about going the PureScript route then? Remove Edit: For reference the PureScript prelude |
I’m -1 on additive group but I didn’t insist since @non liked it so much. I don’t like having a different type just to get syntax (which I’m not an enourmous fan of). Algebird has always done what @LukaJCB suggests and uses types (case classes sadly) to dispatch. This means we get weird stuff like When writing generic code, I don’t care if I have an additive or multiplicative group. So at those points either and implicit or manual conversion is done. Since I’m most interested in generic programming and not syntax hacks, this seems like a bad trade. |
Another argument is that the We're probably going to integrate
I agree with this and a function def foo[A: AdditiveMonoid](...) = ... is completely the same as def foo[A: Monoid](...) = ... or def foo[A: MultiplicativeMonoid](...) = ... The laws are the same and they are fundamentally equivalent. IMO, generic code shouldn't care about multiplicative or additive at that level of abstraction. The fact that we could remove 12 type classes from Algebra without losing any functionality is a pretty strong argument to me personally :) |
I see the force is strong on this one. Before I present my case, let's acknowledge that there is no optimal solution there, just sets of different trade-offs. The ambition of Spire is to go beyond arithmetic, and support generic algebraic structures: for example polynomials have more or less structure depending on what the underlying scalar supports. So precision is definitely a requirement. On the other hand, PureScript made the choice of having a compact prelude, acknowledging platform limitations: for example, none of the I'll now give use cases of the structures that are proposed for elimination. My own experience concerns the use of Scala in quantum information, where the concepts below are central (complex arithmetic in particular).
|
@denisrosset can you speak to why the newtype approach does not solve the problems? Is it just that you don't want to pick a privileged monoid from a ring? |
I haven't seen the newtype approach implemented in other symbolic computation systems. For example, both GAP and SAGE duplicate the structure into additive and multiplicative variants (note: both use multiplicative notation for monoids/groups by default). The newtype approach is fine when you are just using a generic monoid. In the context of linear algebra, the full precision is precious + the coherence of the whole is precious. See for example https://github.com/denisrosset/scalin/blob/master/core/src/main/scala/scalin/Mat.scala where different operations become available on a matrix depending precisely on the scalar type (the For example, In the end, maybe cats/algebra does not require per se such a fine grained distinction. But the possibility of making such distinctions downstream is precious. |
Fully agreed, and thanks for putting in the effort to fully explain your position and bearing with me 😄
That SAGE link doesn't appear to be working.
This is interesting and seems sensible to me, however I'm still a bit unclear as to why you definitely need an I think I have more to say on this, but I'm out of time right now and will continue this soon :) Thanks again for your time! |
Corrected the SAGE link, thanks for the comment! Before replying to your points, let's mention that I've been bitten by the Scala typesystem when trying to encode mathematical structures using categorical concepts. It's simply not powerful enough to encoding structures that e.g. SAGE or Magma (another CAS) implement. So trade-offs are needed. Spire does not aim at providing an abstract framework for all math, but basically "undergraduate mathematics", i.e. the default context you get when running Mathematica or Maple. To answers your points. There are data types that support only multiplicative semigroups. As a concrete example, the monomial part of polynomials (e.g. As for additive groups, vector spaces form an additive group without a corresponding multiplicative group. Now, to clarify my position:
|
Denis. I have a PhD in quantum information and computation and an undergrad degree in math. I mention that because it feels like the argument in this thread is being made from a position of authority: “I am doing some fancy stuff and I assure you it is only possible if the abstraction works exactly this way”. I think this is not a way to argue for something and I think if we can’t make a concrete case for many cars users these abstractions don’t belong in cats. What I think we need to see some stronger arguments for cats to include these types for any reason other than we like you and @non and y’all just really like them, which I guess can be okay in some limited cases. Cats largely is about lawful abstractions. Can you clarify any law an additive monoid has to follow that a multiplicative does not or vice versa? I am aware of no such law in algebra. As far as I can see they exist in order to distinguish the two structures in rings. Rings have laws but independently these additive and multiplicative structures don’t have separate laws. Can you show some actual code you can’t see how to generalize which you can do lawfully using additive/multiplicative? We need to be more concrete. The default position is non-inclusion. So I think the include case needs to be made and more concretely in my view. |
Please point me where I made an argument of authority. I think I provided quite a few examples, and I'm sincerely curious about how the same algebraic structures could be encoded with a I'm not adamant for the inclusion of algebra into cats, if that's the sticking point. (Actually, I'm curious about the use of ring structures in Algebird; can you point me to relevant examples?) I'm adamant on preserving the syntactic distinction between additive and multiplicative structures in Spire, as is standard in other computer algebra systems (see links above). Finally, great that you were active in quantum information! I did not expect that at all. |
For me personally, I'm going to list a few reasons why I think having ring structures in cats would be great:
import cats.implicits._
import algebra.instances.all._
1 |+| 1 // value |+| is not a member of Int
|
On Mon, Oct 8, 2018 at 8:24 PM Luka Jacobowitz <[email protected]> wrote:
The lack of finer grained numeric typeclasses in cats makes defining
abstract programs with numerics much more difficult, many people I know
opting instead for the stdlib Numeric type, which I think is a fairly bad
sign. One example of this is newts
<https://github.com/julien-truffaut/newts/blob/master/core/shared/src/main/scala/newts/Mult.scala>
which offers a Mult newtype for any N: Numeric and there are many others
out there. This could be much nicer IMO.
+1 to that. Numeric is too coarse grained to be a good abstraction, and
with algebra merging into Cats we should finally reach a solution in Scala
ecosystem that is both (a) fine grained, and (b) widely available.
Actually Ive been trying since 2011 to see this happen, eg
https://issues.scala-lang.org/browse/SI-5202 .
|
The main problem imho is that the Scala type system is not powerful enough to declare distinct instances of the same typeclass on a given object and combinations thereof (barring newtypes, but I wonder how one would write arithmetic or boolean operations with that syntax). (In textbooks, you can summon as many instances as you wish by defining new notation, and God knows how many LaTeX symbols are out there.) As an additional example: Now, to move forwards. We don't wish to duplicate all the |
Quick addendum to the Alternative Validation, I coded up a quick example of what we could do if Semiring were available in Cats-core :) case class MatChain[A](value: Chain[Chain[A]])
sealed trait AltValidation[+E, +A]
case class Valid[A](a: A) extends AltValidation[Nothing, A]
case class Invalid[E](e: E) extends AltValidation[E, Nothing]
type ValidatedMat[E, A] = AltValidation[MatChain[E], A]
def validateInt(s: String): ValidatedMat[String, Int] =
try(Valid(s.toInt)) catch {
case e: Throwable => Invalid(MatChain.one("No number"))
}
def validateBoolean(s: String): ValidatedMat[String, Boolean] =
if (s.toLowerCase == "true") Valid(true)
else if (s.toLowerCase == "false") Valid(false)
else Invalid(MatChain.one("Not a bool"))
def validateIntOrBoolean(s: String): ValidatedMat[String, Either[Int, Boolean]] =
validateBoolean(s).map(_.asRight[Int]) <+>
validateInt(s).map(_.asLeft)
val validInt = "42"
val validBool = "True"
val invalid = "Nope"
println(validateIntOrBoolean(validInt)) // Valid(Left(42))
println(validateIntOrBoolean(validBool)) // Valid(Right(true))
println(validateIntOrBoolean(invalid)) // Invalid(MatChain(Chain(Chain(Not a bool), Chain(No number)))) |
What is |
It's just a newtype over |
As for other things to read, the only thing I've got is the inspiration from PureScript: https://pursuit.purescript.org/packages/purescript-validation/3.0.0/docs/Data.Validation.Semiring |
@johnynek I've just met with people in Siegen working on a formal approach to the description of algebraic structures. Right now, they are working on top of a dynamically typed language (GAP), but assured me that their approach solves a lot of ambiguities. Their system is here: http://homalg-project.github.io/CAP_project/CAP/doc/chap0.html , but I haven't taken the time yet to investigate (they have examples and tutorials, but you have to look for them). It would be interesting to see if our Scala instances can benefit from their principled approach. |
This is really cool, thanks for sharing! |
@johnynek Thanks a lot for the insightful comments and the debate. It led to a talk at the Lausanne Typelevel summit, and many discussions with people in the industry. Especially
Sometimes it is worth losing a bit of performance / expressiveness if it means getting community traction. |
I'd love to watch the talk and view the slides. Please post them if possible! |
Right now, there's
AdditiveSemigroup
andMultiplicativeSemigroup
which are then combined intoSemiring
. This is all well and good, but unfortunately there's a huge amount of code out there written on the basis ofcats.kernel.Semigroup
and it's a bit of a shame we can't reuse those.Especially in Spire where the various numeric types don't have
Monoid
instances and therefore can't befoldMap
ed.Since in the Cats ecosystem numeric types usually come with an Additive Monoid as default, I would personally love to see that convention being expanded in this project, but I also understand there are some good reasons not to do that.
Here is some code I came up with that demonstrates how I'd personally like to have it:
The text was updated successfully, but these errors were encountered: