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

generic tuple not of subtype of ProductN #15253

Open
m8nmueller opened this issue May 20, 2022 · 10 comments
Open

generic tuple not of subtype of ProductN #15253

m8nmueller opened this issue May 20, 2022 · 10 comments

Comments

@m8nmueller
Copy link

Compiler version

3.1.2

Minimized code

case class Foo(a: Int, b: String)
val foo = Foo(1, "Hello")
var x: Tuple2[Int, String] = Tuple.fromProductTyped(foo)
var y: Product2[Int, String] = x
var z: Product2[Int, String] = Tuple.fromProductTyped(foo)

Output

-- [E007] Type Mismatch Error: -------------------------------------------------
5 |var z: Product2[Int, String] = Tuple.fromProductTyped(foo)
  |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |                               Found:    (Int, String)
  |                               Required: Product2[Int, String]

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

Tree: Tuple.fromProductTyped[Foo](foo)(
  Foo.$asInstanceOf[
    
      (
        deriving.Mirror.Product{
          MirroredType = Foo; MirroredMonoType = Foo; MirroredElemTypes <: Tuple
        }
       & 
        scala.deriving.Mirror.Product{
          MirroredMonoType = Foo; MirroredType = Foo; 
            MirroredLabel = ("Foo" : String)
        }
      ){
        MirroredElemTypes = (Int, String); 
          MirroredElemLabels = (("a" : String), ("b" : String))
      }
    
  ]
)

I tried to show that
  (Int, String)
conforms to
  Product2[Int, String]
but the comparison trace ended with `false`:
          
  ==> (Int, String)  <:  Product2[Int, String]
  <== (Int, String)  <:  Product2[Int, String] = false

The tests were made under the empty constraint

Expectation

As (A, B) = Tuple2[A, B] is a direct subclass of Product2[Int, String], I would expect that the assignment to z compiles.

@m8nmueller m8nmueller added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels May 20, 2022
@bishabosha bishabosha changed the title Result of Tuple.fromProductTyped not of type ProductN generic tuple not of subtype of ProductN May 21, 2022
@nicolasstucki nicolasstucki added area:library Standard library and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels May 23, 2022
@nicolasstucki
Copy link
Contributor

This is consistent with the current design as Tuple2[A, B] <:< (A, B) and not equal. Generic tuples are subtypes of Product.

@nicolasstucki
Copy link
Contributor

nicolasstucki commented May 23, 2022

Maybe the mirrors could return MirroredElemTypes as Tuple2[A, B] instead of a (A, B) but this might have other unintended consequences.

@m8nmueller
Copy link
Author

I don't know the compiler good enough to anticipate the results, but I think it is rather useful feature in absence of the traditional "unapply".

My use case are circe's Encoder#forProductN, which require a function from (case class) instance to ProductN. Are there other possibilities to implement that trivially or should that interface be changed?

@nicolasstucki
Copy link
Contributor

One of the long terms goals of generic tuples is to make the TupleN encoding redundant and at some point remove it and only keep one kind of tuple. This also implies that ProductN would not be part of tuples.

My use case are circe's Encoder#forProductN, which require a function from (case class) instance to ProductN. Are there other possibilities to implement that trivially or should that interface be changed?

It seems that you need to use mirrors to implement that. What does Encoder#forProductN do exactly?

@m8nmueller
Copy link
Author

One of the long terms goals of generic tuples is to make the TupleN encoding redundant and at some point remove it and only keep one kind of tuple.

Sounds promising :)

What does Encoder#forProductN do exactly?

You can encode any object as JSON by mapping it to a product (tuple) of objects which can be encoded by other means.

@m8nmueller
Copy link
Author

This is consistent with the current design as Tuple2[A, B] <:< (A, B) and not equal.

But why does the assignment to x work, then? If fromProductTyped returns (Int, String) which is not <:< Tuple2[…], the variable type should mismatch.

@SethTisue
Copy link
Member

SethTisue commented Sep 15, 2022

It's really.... shall we say, disquieting that the subtyping here isn't transitive:

scala> summon[Int *: Int *: EmptyTuple <:< Tuple2[Int, Int]]
val res11: (Int, Int) =:= (Int, Int) = generalized constraint
                                                                                                    
scala> summon[Tuple2[Int, Int] <:< Product2[Int, Int]]
val res12: (Int, Int) =:= (Int, Int) = generalized constraint
                                                                                                    
scala> summon[Int *: Int *: EmptyTuple <:< Product2[Int, Int]]
-- Error: ----------------------------------------------------------------------
1 |summon[Int *: Int *: EmptyTuple <:< Product2[Int, Int]]
  |                                                       ^
  |                      Cannot prove that (Int, Int) <:< Product2[Int, Int].
1 error found

🙀

It's rather cold comfort to know that Tuple2 might eventually go away in some possible future.

@rossabaker
Copy link

This is consistent with the current design as Tuple2[A, B] <:< (A, B) and not equal.

Is that the future design or the current design? They seem currently equal to me:

  • There is a witness they're equal:

    summon[(Int, Int) =:= Tuple2[Int, Int]]
  • The graph on Tuple2 docs starts at class (T1, T2), as though they're synonymous.

@bplommer
Copy link
Contributor

bplommer commented May 2, 2023

It's really.... shall we say, disquieting that the subtyping here isn't transitive:

Agreed - I'm struggling to see how this issue is an “enhancement” rather than a bug.

@matthughes
Copy link

Fixing this could make it a lot easier to move away from derivation. This seems like it should compile:

case class Foo(x: Int, y: String)
val t: Tuple2[Int, String] = Tuple.fromProductTyped(Foo(1, "a"))
val p1: Product2[Int, String] = p1

// fails to compile: Found (Int, String); required Product2[Int, String]
val p2: Product2[Int, String] = Tuple.fromProductTyped(Foo(1, "a"))

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

6 participants