-
Notifications
You must be signed in to change notification settings - Fork 9
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
simplify coulomb's implicit type system #537
base: scala3
Are you sure you want to change the base?
Conversation
I expect this to fail CI for a while, as I rewrite it in pieces |
The one Im not keen on here is
This means, using Coulomb would automatically pull in Spire right? Spire is a large and largely unmaintained, or at least auto-pilot, codebase. Much of it written by Non who has been inactive in Scala for years. It contains some fantastic code and ideas but it also spans a very wide range of features in a single monolithic library. Once a library is a dependency of a project, it can be very hard to prevent usage of it throughout a codebase. So pulling in Spire may create an additional hurdle for people to embrace Coulomb. Is Rational the main thing Coulomb needs from Spire? |
@benhutchison spire is currently maintained by @armanbilge. To a certain degree I share your concerns. Spire is no longer under active development, OTOH at some point One might ask the same questions for coulomb, hypothetically. It's certainly not a problem now, but some day off in The Future, I might not want to do this any more 😂 Ideally, if and when that day comes, I'll have handed off the sustaining-engineering to someone in typelevel. Regarding |
@benhutchison as Erik says, I am currently maintaining Spire. We can definitely consider any concrete ideas you have to improve it, without breaking compatibility of course :) |
@armanbilge I figured out how to make
|
I don't think I properly conveyed my concern with depending on Spire. In a nutshell, it's the "kitchen sink" approach - its large, complex and contains a lot of varied stuff. Not bad or low-quality stuff, quite the opposite. But alot of stuff. The "unmaintained" comment was primarily to imply it has hardened up and seems unlikely to ever undergo modularisation into smaller units. Perhaps unlikely to see the TODO gaps in the documentation ever filled out. Coulomb may want ATM Coulomb core is a working, useful module that provides significant value without a Spire dependency. I see that as worthwhile preserving if possible. |
@benhutchison possibly "better modularization" is a feature of interest for spire that @armanbilge could consider. I'm not sure what a better modularization would look like, but if it can be done it seems useful. coulomb already includes typelevel Frankly I'd be interested in Scala itself adopting spire's design for value conversions. Scala's current system treats |
@benhutchison I encourage you to read and comment on this issue: |
OK, decision accepted :) You've made your thinking clear @erikerlandson and I appreciate your arguments are reasonable even if personally I probably prefer the "status quo" design. |
I was planning to refactor the constant-factor syntax enhancements, but I don't think any of it works right currently, see: I am going to just remove all of it. I will put it back, refactored, if scala 3.4 proves to fix it. |
As long as I am committing to onboarding As part of this process, I have also rewritten |
Using |
I wanted to revisit the question of Here is the current opaque type based system, note I am using an object googoo:
import syntax.*
val q1 = 1.0.withUnit[1000]
val q2 = q1.toUnit[1] The resulting bytecode looks like this:
Now here is an equivalent bit of code using object hoohoo:
import qvcsyntax.*
val q1 = 1.0.asQVC[1000]
val q2 = q1.toUnit[1] And its bytecode:
|
@armanbilge @benhutchison I think that It's also a bit easier for me to reason about - the |
This seems like a very powerful idiom to me: inline def toUnit[U]: Quantity[VL, U] =
import coulomb.conversion.coefficients.*
inline compiletime.erasedValue[VL] match
case _: Double =>
(coefficientDouble[UL, U] * ql.value.asInstanceOf[Double])
.asInstanceOf[VL]
.withUnit[U]
case _ =>
compiletime.summonFrom {
case conv: UnitConversion[VL, UL, U] =>
conv(ql.value).withUnit[U]
case _ =>
compiletime.error("no!")
} It allows you to have your cake and eat it too: you can really optimize for certain special cases, but still allow the compiler to fall back on typeclasses for anything else. |
I'm wary about switching opaque types back to AnyVal.
|
I also filed an RFE to inline spire typeclass defs: |
@benhutchison my ultimate plan is to define collections that explicitly store raw values under the hood, and so it would not matter if it was However, if |
Yes, using Scala 3 is a mixture of tantalizing capabilities but also frustrating limitations. However, I do think they've signalled that opaque types are the "blessed path", although it will likely take time to see improvements. Competing priorities etc. But looking at the recent 3.4.0 RC1 release notes, it is clear there's alot of work going on.. |
@armanbilge I ran into this interesting glitch: |
@armanbilge on scala/scala3#19523 odersky made a comment:
I'm not sure what he was talking about; I thought |
conversation on scala discord:
I don't know how this sits with me, but I do know that supporting And they appear to have no intention of "fixing" this, they don't think Eliminating implicit unit/value conversions would even further simplify coulomb's systems. |
A couple observations from morning experiments: 1: I have been Doing It Wrong. I've been calling inline def toValue[V](using
conv: ValueConversion[VL, V]
): Quantity[V, UL] =
conv(ql.value).withUnit[UL] // withUnit call isn't needed here!
inline def toValue2[V](using
conv: ValueConversion[VL, V]
): Quantity[V, UL] =
conv(ql.value) // better bytecode! 2: when I started all this, the compiler was not allowing me to use // all these inline seem cool with scala compile now
inline def apply[V, U](v: V): Quantity[V, U] = v
inline def apply[U](using a: Applier[U]) = a
final class Applier[U]:
inline def apply[V](v: V): Quantity[V, U] = v
object Applier:
inline given ctx_Applier[U]: Applier[U] = new Applier[U] 3: // use call to `Quantity[V,U](v)` instead of withUnit here
inline def withUnit[U]: Quantity[V, U] = Quantity[V, U](v) |
update on (2) above: the compiler allows |
I am leaning toward removing |
Using a combination of:
I can get this: object foofoo:
import coulomb.*, coulomb.syntax.*
import coulomb.policy.standard.given
import coulomb.policy.algebras.given
val z = 1d.withUnit[1] + 1d.withUnit[1000] to compile pretty much into my dream, where it is just raw values and raw constant multiplies, adds, etc:
|
I found a bug with in-lining functions |
1c099ee
to
0b38639
Compare
I wanted to update you on my planned changes that are most impactful
I have been toying with the idea of distinguishing "safe" value conversions, such as Int -> Long, or Float -> Double, etc, from unsafe ones, such as Double -> Float, Long -> Double, etc. This would require something like |
Thanks for the summary @erikerlandson This will affect my application, but likely just requiring some porting work and not any blockers. I'll post some feedback when i do the port, but that's unlikely to be for a while, ie 1-3 months, due to other commitments. |
196828e
to
389950d
Compare
This is a problem for the dependent type signatures of |
ceae7bd
to
c6b3979
Compare
e3d2906
to
91cc146
Compare
91cc146
to
e415644
Compare
@benhutchison @armanbilge I have cut a release candidate for this refactor: |
Hi Erik, I have been quite distracted as, a few days after writing this back in April, my partner discovered she is pregnant. So current attention focussed on 👶 not 💻. Now seems possible I won't have opportunity to migrate to latest coulomb until I take some extended months of parental leave, end of year. And even that depends on whether she's a good 👼 or bad 😈 baby.. |
@benhutchison congratulations 🎉 No hurry, you can see it's been taking me a while to work through this, and I don't have a baby 😁 |
@danielrmeyer if you have a chance I'd be interested in learning how |
This will be to experiment with ideas in #534.
The main simplifications will be:
Add
and friends, folding everything back into operator signaturesbefore final merge