-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bring Ring-like structures to ZIO Prelude #351
Conversation
I think the cornerstone of this hierarchy should be In addition, a distributive is really a pair of other binary operators that satisfy certain laws. So that would argue the type class itself should contain a proof of those, possibly in the form of instances for the other two binary operators. In fact, maybe there's some encoding where a type class is parameterized by the properties of the two binary operators, e.g.: trait Distributive[Add[x] <: Associative[x], Multiply[x] <: Associative[x], A] {
def add: Add[A]
def multiply: Multiply[A]
} |
Thanks John for looking at this! DistributiveMultiply[A, Addition = CommutativeMonoid, Multiplication = Identity] isn't enough. There is also another law specifying that 0 (neutral element for addition) annihilates when used in multiplication. This is where AnnihilatingZero[A, Addition = CommutativeMonoid, Multiplication = Identity] Both (I opted for more verbose Does that make sense? Btw one thing I was considering is whether the Addition and Multiplication proofs should be straight on def Addition: Addition[A]
def Multiplication: Multiplication[A] but I settled on def Addition: Addition[Sum[A]]
def Multiplication: Multiplication[Prod[A]] The reason is that since "adding" and "multiplying" the vales are both valid ways of combining them, us (ZIO Prelude developers) and our users, will defined Type class instances not directly on |
I've reworked the proposal a bit, can you @jdegoes have another look, please? One can start by having a look at the diagram (linked in the PR description). Now, there are only The other traits were demoted to mere "Shape" traits. These are just helper traits containing some useful type parameters and/or methods. But otherwise, they're not considered Type classes, because they don't come with any interesting properties (i.e. laws), maybe besides that the operation(s) implemented by their method(s) are total. I've borrowed the term from this ticket scala/scala3#9028 (which borrows it from the Getting F-Bounded Polymorphism into Shape paper). I think this move was a great improvement, because it keeps with the ZIO Prelude principle that each Type class must come with a set of laws. One way of looking at it is that the (nominal) type of the Type class trait is the law(s). We double down on deconstructing things and composing them (these individual laws) horizontally via Scala's intersection types, instead of creating vertical hierarchies (like in Haskell/ScalaZ/Cats). |
Following up on a conversation @sideeffffect and I had today, I think one encoding of this we could explore is: package zio.prelude
trait Semiring[A] {
def add(a1: A, a2: A): A
def multiply(a1: A, a2: A): A
}
object Semiring {
val laws = ???
// Distributive law
implicit def derive(implicit sum: Associative[Sum[A]], product: Product[A]): Semiring[A] =
new Semiring[A] {
def add(a1: A, a2: A): A
def multiply(a1: A, a2: A): A
}
}
trait SemiringWithIdentity[A] extends Semiring {
def identity: A
}
object SemiringWithIdentity {
val laws = ???
// annihilation
implicit def derive(implicit sum: Inverse[Sum[A]], product: Product[A]): SemiringWithIdentity[A] =
???
} The idea is that we should be able to derive a I don't think this adds functionality beyond what you already have but it would be significantly simpler (no type members, no Aux pattern, etc...). I also like it conceptually in that it makes the individual binary operations fundamental and then this type class just describes that there are two binary operations that are related in a certain way. It also avoids the need for any new implementations of the type classes because we can just delegate to the existing instances. One consideration is that the validity of the instances here depend on the validity of the instances that the user defines for |
# Conflicts: # core/shared/src/main/scala/zio/prelude/AddMultiplyShape.scala # core/shared/src/main/scala/zio/prelude/Annihilation.scala # core/shared/src/main/scala/zio/prelude/DistributiveMultiply.scala # core/shared/src/main/scala/zio/prelude/DivideShape.scala # core/shared/src/main/scala/zio/prelude/Inverse.scala # core/shared/src/main/scala/zio/prelude/InverseNonZero.scala # core/shared/src/main/scala/zio/prelude/SubtractShape.scala # core/shared/src/test/scala/zio/prelude/AnnihilationSpec.scala # core/shared/src/test/scala/zio/prelude/DistributiveMultiplySpec.scala # core/shared/src/test/scala/zio/prelude/InverseNonZeroSpec.scala # docs/overview/diagrams.md
Unfortunately, for this Ring-like thing to work, |
Okay. Let me look in more detail. My concern is more about overall increase in complexity of the hierarchy for core functional abstractions by adding new ones that I think largely only have applicability in the math context (you basically want to describe division). |
@adamgfraser Agree with Adam! Would be good to get this in, and just under an experimental package so we have the flexibility to evaluate it for a while. Thank you for your persistence on this! |
# Conflicts: # docs/overview/diagrams.md # experimental/shared/src/main/scala/zio/prelude/experimental/coherent/coherent.scala
I know this has literally been years, but I'd appreciate if any interested parties could have another look at this 🙏 |
/cc @kyri-petrou |
@sideeffffect I'm not sure I'm the best person to review this because my understanding of algebras is basic at best. Are there any specific parts of the code you'd like me to focus on? |
The most important thing to check is that it doesn't break any thing in the non-experimental parts. So if some match in the experimental part is "broken", doesn't really matter that much. We can always fix it later. The experimental parts are wild-west with not backward guarantees, but wouldn't want to release ZIO Prelude 1.0 with broken non-experimental parts. After this is merged, it would be wise to release another RC and give some time for people to test it out. Only after that, we should release 1.0.0. Does that sound good @ghostdogpr ? |
Sounds good 👍 |
I'll need one more approval and then let's merge 💪 |
This is the foundation to have Ring (and the whole family, like Semiring, Field, ...) algebraic structures and instances for Scala library/ZIO types.
You can see the skeleton of the hierarchy here:
https://github.com/sideeffffect/zio-prelude/blob/ring/docs/functional-abstractions/abstraction-diagrams.md (named DistributiveProd)
More is still to come, mainly comments/documentation and the instances for more types, currently there is just
Int
andDouble
to try things out.I would be interested in your feedback 😃
/cc @adamgfraser @jdegoes