Iltotore / iron

Strong type constraints for Scala
https://iltotore.github.io/iron/docs/
Apache License 2.0
457 stars 43 forks source link

fix: Ambiguous given instance inside `RefinedTypeOps` #180

Closed Iltotore closed 12 months ago

Iltotore commented 1 year ago

Closes #178

@ajaychandran can you check if this PR solves your problem?

ajaychandran commented 1 year ago

This should work (waiting for the nightly version) but perhaps it would be better to grant access to the given instance (to avoid re-synthesis)?

Iltotore commented 1 year ago

perhaps it would be better to grant access to the given instance

If we do that, the given instance would leak. For example, this code would compile:

import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.Positive

opaque type Temperature = Int :| Positive
object Temperature extends RefinedTypeOps[Int, Positive, Temperature]
//No explicit iron import
import foo.Temperature

summon[RuntimeConstraint[Int, Positive]] //Works

which is not the expected behaviour. Also I'm afraid this would cause other ambiguous given errors:

opaque type Temperature = Int :| Positive
object Temperature extends RefinedTypeOps[Int, Positive, Temperature]

opaque type Moisture= Int :| Positive
object Moisture extends RefinedTypeOps[Int, Positive, Moisture]
import foo.{Moisture, Temperature}

summon[RuntimeConstraint[Int, Positive]] //Ambiguous given errors: both Moisture.rtc and Temperature.rtc are eligible

(to avoid re-synthesis)

the inline def does not re-synthetize nor add any overhead on top of the protected given RuntimeConstraint. The method call is erased after compilation.

ajaychandran commented 1 year ago

(to avoid re-synthesis)

I am using RuntimeConstraint to define typeclass instances for constrained opaque types. These instances need to be defined explicitly to work with Scala 3 auto derivation (for types using the constrained type). Having access to the RuntimeConstraint within the companion object would avoid re-synthesis.

I think protected access will support mine and the other use cases. Right?

ajaychandran commented 1 year ago

From the workaround in the ticket,

type IsWhole = GreaterEqual[0]
opaque type Whole <: Int = Int :| IsWhole
object Whole extends RefinedTypeOps[Int, IsWhole, Whole]:

  given Parse[Whole] =
    summon[Parse[Int]].refineParse(using rtc)

I would like to avoid having to specify the given instance (using rtc) and reuse the one from RefinedTypeOps.

  given Parse[Whole] =
    summon[Parse[Int]].refineParse
Iltotore commented 12 months ago

You shouldn't have to. That's why the protected is given

Iltotore commented 12 months ago

This code compiles with this PR:

import io.github.iltotore.iron.*
import io.github.iltotore.iron.constraint.numeric.GreaterEqual

trait Parse[A]:

  def refineParse[C](using contraint: RuntimeConstraint[A, C]): Parse[A :| C] = ???

given Parse[Int] = new Parse[Int]{}

type IsWhole = GreaterEqual[0]
opaque type Whole <: Int = Int :| IsWhole
object Whole extends RefinedTypeOps[Int, IsWhole, Whole]:

  given Parse[Whole] = summon[Parse[Int]].refineParse
ajaychandran commented 12 months ago

Thanks for checking.

I was getting compiler errors (something about deferred inline) with inline instances but that could have been my code.

Iltotore commented 12 months ago

No problem. Feel free to open a new discussion or issue if you have hard time dealing with this error.