-
Notifications
You must be signed in to change notification settings - Fork 45
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
Allow refinement of polymorphic types in non-inlined methods / givens #174
Comments
The AFAIK you cannot derive a Can you share the implementations you tried? |
I think that as I started typing down what I tried, I managed to somewhat accidentally figure it out? This seems like something that would work right? final class RuntimeConstraint[A, B](_test: A => Boolean, _message: String) {
def test(value: A): Boolean = _test(value)
def message: String = _message
}
object RuntimeConstraint {
inline given derived[A, B](using c: Constraint[A, B]): RuntimeConstraint[A, B] =
new RuntimeConstraint[A, B](c.test(_), c.message)
} |
I know this might be contradictory to the original issue, but in this case I think we do want to define the Note that this is not an abstract inline method we can invoke it from non-inlined methods normally |
What the generated bytecode looks like when using a given |
It's a bit hard to explain it well, but this is the behaviour from what I understand. Let's assume we have have a constraint as
someInt > 0 && someInt < 10 RuntimeConstraint will create a proxy method such as the one below, which is essentially called wherever def someRandomNameTheCompilerGives(value: Int): Boolean =
value > 0 && value < 10 Now, there are a few things to unpack here. First, let's look at this code: type SingleDigit = Greater[0] & Less[10]
def foo[A, B](value: A)(using c: RuntimeConstraint[A, B]): A :| B =
if c.test(value) then value.assume else ???
val someInt = 2
val a: Int :| SingleDigit = foo(someInt)
val b: Int :| SingleDigit = foo(someInt) In this case, the compiler will create 2 instances of Where the benefit of Perhaps the answer is for the object RuntimeConstraint:
inline def derived[A, B](using c: Constraint[A, B]): RuntimeConstraint[A, B] =
new RuntimeConstraint[A, B](c.test(_), c.message)
object auto:
inline implicit def derivedAuto[A, B](using c: Constraint[A, B]): RuntimeConstraint[A, B] = derived As for Newtypes, I guess we can create one within Hope that was clear (enough) because it's generally quite hard to explain 😓 |
Don't worry: I got it 😛 If I understand the problem correctly, it is especially useful when you use the same constraint multiple times. I also get how reusing the same constraint for newtypes is useful: we could re-use the same proxy function each time but it will probably not work with Here is how inline def apply(value: A :| C): T = value.asInstanceOf[T] As you can see, there is no checking of the value: it is delegated to the implicit refinement conversion: type Temperature = Int :| Positive
object Temperature extends RefinedTypeOps[Temperature] val x: Int :| Positive= ???
val y: Int = ???
Temperature(x) //compiles to `x`
Tempature(y) //implicit refinement at compile-time As for other methods: inline def either(value: A)(using constraint: Constraint[A, C]): Either[String, T] =
Either.cond(constraint.test(value), value.asInstanceOf[T], constraint.message) Becomes: private val constraint = RuntimeConstraint.derived[A, C]
inline def either(value: A): Either[String, T] =
Either.cond(constraint.test(value), value.asInstanceOf[T], constraint.message) but this will break retrocompatibility again:
I am currently sick so maybe I'm missing something. Do you have an idea about using |
Hmm that is indeed an interesting one - I'm not sure whether there's a way that it can be done without breaking binary compatibility, but I believe something along these lines of what's below could work. The final class RuntimeConstraint[A, B](_test: A => Boolean, val message: String) {
inline def test(value: A): Boolean = _test(value)
}
object RuntimeConstraint {
inline given derived[A, B](using c: Constraint[A, B]): RuntimeConstraint[A, B] =
new RuntimeConstraint[A, B](c.test(_), c.message)
}
trait RefinedTypeOpsImpl[A, C, T](using rtc: RuntimeConstraint[A, C]):
inline def either(value: A): Either[String, T] =
Either.cond(rtc.test(value), value.asInstanceOf[T], rtc.message) As for |
I opened a draft PR just as a POC just to demonstrate the full code |
Hi there 👋 I've looked through the sourcecode and couldn't find anything implemented for this so I thought to open an feature request issue. Apologies if this is already possible but might have missed it!
Is your feature request related to a problem? Please describe.
In many cases, we want to create codecs / typeclasses for
IronType
s (json, DB access, etc.). Let's take the circe decoder as an example from theiron-circe
package:The main issue here is that this only works with
inline
givens or methods, because thetest
method inConstraint
is declared inlined.So what's wrong with using an inline given/method? The problem with that is that it will inline the code wherever a
Decoder[A :| B]
is needed. In this example, the effect on compilation time / code size might be minimal, but there are 2 particularly worrying cases:new Decoder{ ... }
on the RHS, since that would a new anonymous class to be created wherever aDecoder[A :| B]
is requiredFunction1
each time it's inlinedDescribe the solution you'd like
It would be good if there was a way to derive a
RuntimeConstraint[A, B]
(in lack of better name) where thetest
/message
methods are not defined as inlined:This should contain the runtime logic needed to test for a polymorphic type without requiring an inlined given / method. e.g., the Decoder given above could be re-written as:
Describe alternatives you've considered
Creating an explicit given for each IronType, but that becomes cumbersome very quickly in a large codebase:
I'd be happy to contribute if needed, but I'll need some pointers on how to approach this. I actually gave it a go with trying to implement this but couldn't get it to work unfortunately
The text was updated successfully, but these errors were encountered: