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

Conversion behave differently depending on if implicit def or given is used #21757

Closed
tribbloid opened this issue Oct 11, 2024 · 9 comments · Fixed by #21785
Closed

Conversion behave differently depending on if implicit def or given is used #21757

tribbloid opened this issue Oct 11, 2024 · 9 comments · Fixed by #21785
Labels
area:implicits related to implicits itype:bug
Milestone

Comments

@tribbloid
Copy link

Compiler version

3.5.1

Minimized code

The following 2 examples demonstrating hypothetical syllogism are almost identical, but the second failed, because implicit def is used instead of given

(example 1)

  object AlmostNewStyle {

    class A(val value: Int)

    class B(val a: A)

    class C(val b: B)

    trait Conv[A, B] extends Conversion[A, B]

    given ab: Conv[A, B] = { (a: A) => new B(a) }

    given bc: Conv[B, C] = { (b: B) => new C(b) }

    object ConvUtils {
      given hypotheticalSyllogism[A, B, C](
          using
          ab: Conv[A, B],
          bc: Conv[B, C]
      ): Conv[A, C] = {

        new Conv[A, C] {
          def apply(a: A): C = bc(ab(a))
        }
      }
    }
    import ConvUtils.given

    def demo(): Unit = {
      val a = new A(42)
      val c: C = a
      println(c.b.a.value) // Outputs: 42
    }
  }

(example 2)

  object OldStyle {

    class A(val value: Int)

    class B(val a: A)

    class C(val b: B)

    trait Conv[A, B] extends Conversion[A, B]

    given ab: Conv[A, B] = { (a: A) => new B(a) }

    given bc: Conv[B, C] = { (b: B) => new C(b) }

    object ConvUtils {
      implicit def hypotheticalSyllogism[A, B, C](
          using
          ab: Conv[A, B],
          bc: Conv[B, C]
      ): Conv[A, C] = {

        new Conv[A, C] {
          def apply(a: A): C = bc(ab(a))
        }
      }
    }
    import ConvUtils.given

    def demo(): Unit = {
      val a = new A(42)
      val c: C = a
      println(c.b.a.value) // Outputs: 42
    }
  }

Output

the error of example 2 is:


[Error] /home/peng/git/dottyspike/core/src/main/scala/com/tribbloids/spike/dotty/TransitiveConversion.scala:109:18: Found:    (a : com.tribbloids.spike.dotty.TransitiveConversion.OldStyle.A)
Required: com.tribbloids.spike.dotty.TransitiveConversion.OldStyle.C
Note that implicit conversions cannot be applied because they are ambiguous;
both given instance ab in object OldStyle and given instance bc in object OldStyle match type com.tribbloids.spike.dotty.TransitiveConversion.OldStyle.Conv[A, B]

Explanation
===========

Tree: a
I tried to show that
  (a : com.tribbloids.spike.dotty.TransitiveConversion.OldStyle.A)
conforms to
  com.tribbloids.spike.dotty.TransitiveConversion.OldStyle.C
but none of the attempts shown below succeeded:

  ==> (a : com.tribbloids.spike.dotty.TransitiveConversion.OldStyle.A)  <:  com.tribbloids.spike.dotty.TransitiveConversion.OldStyle.C class dotty.tools.dotc.core.Types$CachedTermRef class dotty.tools.dotc.core.Types$CachedTypeRef
    ==> com.tribbloids.spike.dotty.TransitiveConversion.OldStyle.A  <:  com.tribbloids.spike.dotty.TransitiveConversion.OldStyle.C class dotty.tools.dotc.core.Types$CachedTypeRef class dotty.tools.dotc.core.Types$CachedTypeRef  = false

Expectation

despite the deprecation warning of implicit keyword, the 2 examples should use the same algorithm and behave identically

@tribbloid tribbloid added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 11, 2024
@dwijnand
Copy link
Member

Expectation

despite the deprecation warning of implicit keyword, the 2 examples should use the same algorithm and behave identically

Your expectation is wrong. There are differences in behaviour that were introduced alongside the keyword change.

@tribbloid
Copy link
Author

tribbloid commented Oct 12, 2024

wow I'm sorry this is new to me, let me try to find some old discussion to figure out the differences ...

Automatic code search algorithms are complex subject, many implementations (sledgehammer in Isabelle/HOL, AlphaProof & LeanAgent in Lean 4) are prototypes and non-deterministic. To be used in Scala production code, users should be informed precisely about its capabilities and boundaries

@tribbloid
Copy link
Author

tribbloid commented Oct 14, 2024

Strange, all the articles and discussion I found appears to suggest that the difference between "given" and "implicit" are purely syntactical (e.g. given permits anonymous instance). One authoritative document (https://docs.scala-lang.org/scala3/reference/changed-features/implicit-resolution.html) even suggest that search algorithms of both are upgraded simultaneously:

This section describes changes to the implicit resolution that apply both to the new givens and to the old-style implicits in Scala 3

I'll try next to compile with Scala 3.5-migration option to try to emit more information (if applicable). But if not applicable, where can I find these differences in behaviour from the source code or a release note?

@prolativ
Copy link
Contributor

These examples are quite confusing. First you define A, B and C as classes but then you use these names as type parameters of hypotheticalSyllogism. If you remove the type parameters there (so that you have given hypotheticalSyllogism(using ... and implicit def hypotheticalSyllogism(using ...) both examples compile.
Or was the intention to make this actually work with these type parameters?

@tribbloid
Copy link
Author

If you remove the type parameters there (so that you have given hypotheticalSyllogism(using ... and implicit def hypotheticalSyllogism(using ...) both examples compile.

But how can I remove type parameters there? they are used in both return type and conversion body.

@prolativ
Copy link
Contributor

Simply like that

object OldStyle {

  class A(val value: Int)

  class B(val a: A)

  class C(val b: B)

  trait Conv[A, B] extends Conversion[A, B]

  given ab: Conv[A, B] = { (a: A) => new B(a) }

  given bc: Conv[B, C] = { (b: B) => new C(b) }

  object ConvUtils {
    implicit def hypotheticalSyllogism(
        using
        ab: Conv[A, B],
        bc: Conv[B, C]
    ): Conv[A, C] = {

      new Conv[A, C] {
        def apply(a: A): C = bc(ab(a))
      }
    }
  }
  import ConvUtils.given

  def demo(): Unit = {
    val a = new A(42)
    val c: C = a
    println(c.b.a.value) // Outputs: 42
  }
}

Now A, B and C inside the body of def hypotheticalSyllogism refer to the classes defined above

@Gedochao Gedochao added area:implicits related to implicits and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 16, 2024
@som-snytt
Copy link
Contributor

Ad for -Wshadow:all

@tribbloid
Copy link
Author

thanks a lot @smarter !

@dwijnand
Copy link
Member

I guess the hard way to answer the question of the differences between implicits and givens can look at the usage of Given, Implicit and GivenOrImplicit...

@WojciechMazur WojciechMazur added this to the 3.6.3 milestone Dec 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:implicits related to implicits itype:bug
Projects
None yet
6 participants