Skip to content
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

export object.given does not respect priorities #15264

Closed
erikerlandson opened this issue May 22, 2022 · 6 comments
Closed

export object.given does not respect priorities #15264

erikerlandson opened this issue May 22, 2022 · 6 comments

Comments

@erikerlandson
Copy link

Compiler version

3.1.2

Minimized code

object repro:
    // analogous to cats Eq, Hash, Order:
    class A[V]
    class B[V] extends A[V]
    class C[V] extends A[V]

    // resolve conflicts with prio trick
    object context extends prio1:
        given[V]: C[V] = new C[V]
    class prio1 extends prio2:
        given[V]: B[V] = new B[V]
    class prio2:
        given[V]: A[V] = new A[V]

    object exports:
        // this will erase prios and fail
        export context.given

Output

If you just import context.given the prio trick works:

Welcome to Scala 3.1.2 (11.0.15, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.

scala> import coulomb.cats.repro.*, coulomb.cats.repro.context.given
                                                                                                                                                                                                                
scala> summon[C[Int]]
val res0: coulomb.cats.repro.C[Int] = coulomb.cats.repro$C@25327ca9
                                                                                                                                                                                                                
scala> summon[B[Int]]
val res1: coulomb.cats.repro.B[Int] = coulomb.cats.repro$B@19257f00
                                                                                                                                                                                                                
scala> summon[A[Int]]
val res2: coulomb.cats.repro.C[Int] = coulomb.cats.repro$C@4441199e

BUT if you try to import exports.given the priority info is lost:

scala> import coulomb.cats.repro.exports.given
                                                                                                                                                                                                                
scala> summon[A[Int]]
-- Error: ----------------------------------------------------------------------
1 |summon[A[Int]]
  |              ^
  |ambiguous implicit arguments: both given instance given_C_V in object exports and given instance given_B_V in object exports match type coulomb.cats.repro.A[Int] of parameter x of method summon in object Predef
1 error found

Expectation

Ideally, the priority information would be preserved when importing via exports.given,
so the given definitions could be composed using scala 3's new export feature.

@nicolasstucki
Copy link
Contributor

nicolasstucki commented May 23, 2022

Self-contained version

object repro:
    // analogous to cats Eq, Hash, Order:
    class A[V]
    class B[V] extends A[V]
    class C[V] extends A[V]

    // resolve conflicts with prio trick
    object context extends prio1:
        given[V]: C[V] = new C[V]
    class prio1 extends prio2:
        given[V]: B[V] = new B[V]
    class prio2:
        given[V]: A[V] = new A[V]

    object exports:
        // this will erase prios and fail
        export context.given

import repro.A
import repro.exports.given
def test = summon[A[Int]]

@nicolasstucki nicolasstucki added area:typer area:implicits related to implicits area:export and removed stat:needs triage Every issue needs to have an "area" and "itype" label area:typer labels May 23, 2022
@odersky
Copy link
Contributor

odersky commented May 26, 2022

That cannot work. I propose to use a different scheme to encode priority as outlined in tests/run/implied-priority.scala

/* New scheme: dummy implicit arguments that indicate priorities
 */
object Priority {
  class Low
  object Low { given Low() }

  class High extends Low
  object High { given High() }
}

object Impl2 {
  given t1[T](using Priority.Low): E[T]("low")
  given t2[T](using Priority.High)(using Arg[T]): E[T]("norm")
}

def test2 = {
  import Impl2.given
  assert(summon[E[String]].str == "low") // No Arg available, so only t1 applies

  { given Arg[String]()
    assert(summon[E[String]].str == "norm") // Arg available, t2 takes priority
  }
}

@odersky odersky closed this as completed May 26, 2022
@erikerlandson
Copy link
Author

@odersky that is very cool, and I'm interested in trying to use it. It seems to work in some cases but not others (which I would need):

object priority:
    // lower number = higher priority
    class Prio0 extends Prio1
    object Prio0 { given Prio0() }

    class Prio1 extends Prio2
    object Prio1 { given Prio1() }

    class Prio2
    object Prio2 { given Prio2() }

object repro:
    // analogous to cats Eq, Hash, Order:
    class A[V]
    class B[V] extends A[V]
    class C[V] extends A[V]

    class Q[V]

    object context:
        // prios work here, which is cool
        given[V](using priority.Prio0): C[V] = new C[V]
        given[V](using priority.Prio1): B[V] = new B[V]
        given[V](using priority.Prio2): A[V] = new A[V]

    object exports:
        // so will these exports
        export context.given

    // if you import these don't import from 'context' above
    object qcontext:
        // base defs, like what you would get from cats
        given B[Int] = new B[Int]
        given C[Int] = new C[Int]

        // these seem like they should work but don't
        given[V](using p0: priority.Prio0, c: C[V]): C[Q[V]] = new C[Q[V]]
        given[V](using p1: priority.Prio1, b: B[V]): B[Q[V]] = new B[Q[V]]
        given[V](using p2: priority.Prio2, a: A[V]): A[Q[V]] = new A[Q[V]]

object test1:
    import repro.*
    import repro.exports.given

    // these will work
    val a = summon[A[Int]]

object test2:
    import repro.*
    import repro.qcontext.given

    // this one will fail as ambiguous - prios aren't having an effect
    val a = summon[A[Q[Int]]]

@odersky
Copy link
Contributor

odersky commented May 29, 2022

It works if you keep the "priority" parameters using separate from the rest.

object priority:
    // lower number = higher priority
    class Prio0 extends Prio1
    object Prio0 { given Prio0() }

    class Prio1 extends Prio2
    object Prio1 { given Prio1() }

    class Prio2
    object Prio2 { given Prio2() }

object repro:
    // analogous to cats Eq, Hash, Order:
    class A[V]
    class B[V] extends A[V]
    class C[V] extends A[V]

    class Q[V]

    object context:
        // prios work here, which is cool
        given[V](using priority.Prio0): C[V] = new C[V]
        given[V](using priority.Prio1): B[V] = new B[V]
        given[V](using priority.Prio2): A[V] = new A[V]

    object exports:
        // so will these exports
        export context.given

    // if you import these don't import from 'context' above
    object qcontext:
        // base defs, like what you would get from cats
        given gb: B[Int] = new B[Int]
        given gc: C[Int] = new C[Int]

        // these seem like they should work but don't
        given gcq[V](using p0: priority.Prio0)(using c: C[V]): C[Q[V]] = new C[Q[V]]
        given gbq[V](using p1: priority.Prio1)(using b: B[V]): B[Q[V]] = new B[Q[V]]
        given gaq[V](using p2: priority.Prio2)(using a: A[V]): A[Q[V]] = new A[Q[V]]

object test1:
    import repro.*
    import repro.exports.given

    // these will work
    val a = summon[A[Int]]

object test2:
    import repro.*
    import repro.qcontext.given

    // this one will fail as ambiguous - prios aren't having an effect
    val a = summon[A[Q[Int]]]

odersky added a commit to dotty-staging/dotty that referenced this issue May 29, 2022
odersky added a commit that referenced this issue May 29, 2022
@erikerlandson
Copy link
Author

@odersky that is very interesting - is that because the first layer of currying is now all the same form/signature, so the priorities dominate the ordering?

@erikerlandson
Copy link
Author

@odersky also it makes me wonder - if this is an effective "general" way to overlay priorities onto implicit resolutions, maybe it would be a useful addition to scala.util. You'd have to pick some maximum number of prio layers, but I've never defined such priorities more than 4 layers deep, and I'd imagine 10 or 20 would cover most people's needs. Like the old 22 tupes...

bishabosha pushed a commit to dotty-staging/dotty that referenced this issue Oct 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants