-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Empty Tuple is not instance of Product #9033
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
Comments
Unfortunately, as long as the empty tuple is undistinguishable from unit, this is unfixable. The instance of |
@sjrd Well, I would be much more satisfiet if the behaviour was the following ().asInstanceOf[Tuple].isInstanceOf[Product] // true
().isInstanceOf[Product] // false Because currently we can get a runtime error in a case like the following: scala> val t:Tuple = ()
val t: Tuple = ()
scala> summon[Mirror.Of[...]].fromProduct(t.asInstanceOf)
java.lang.ClassCastException: class scala.runtime.BoxedUnit cannot be cast to class scala.Product (scala.runtime.BoxedUnit and scala.Product are in unnamed module of loader sbt.internal.classpath.ClassLoaderCache$Key$CachedClassLoader @37c469b4) |
That would run contrary to a core expectation that
There's an |
I was aiming more to (():Tuple).isInstanceOf[Product] // true
(():Unit).isInstanceOf[Product] // false Does it make more sense? |
Ah sorry, I had completely misunderstood. Yes, that makes much more sense, and in principle I would like to see that happen. I am not sure what the ramifications of that would be, though. It might prove impractical, or prone to more dangerous warts. |
Well, I checked how is it possible that shapeless-3 is not having issues with this feature and there is a custom I suppose that this is a known feature which was widely discussed internally. There seem to be other related functionality in place. scala> ((1,2):Tuple).isInstanceOf[Product]
val res7: Boolean = true
scala> ((1,2):Tuple):Product
1 |((1,2):Tuple):Product
| ^^^^^^^^^^^
| Found: Tuple
| Required: Product Anyway, I'd want to raise a concern about having inconsistencies such as ^^ in the type system. I'd expect to be able to execute the following statement |
That's not an inconsistency. This happens all the time in all object-oriented type systems. scala> trait A; trait B; class C extends A with B
trait A
trait B
class C
scala> (new C(): A).isInstanceOf[B]
val res0: Boolean = true
scala> (new C(): A): B
^
error: type mismatch;
found : A
required: B |
@sjrd oh my... I did not know that That explains the behaviour but it is rather surprising to me. Since the trait is |
That's correct. |
Currently we define these types, which do not exist at runtime. sealed trait Tuple extends Any { ... }
sealed trait NonEmptyTuple extends Tuple { ... }
sealed abstract class *:[+H, +T <: Tuple] extends NonEmptyTuple { ... } All tuple types are made a subtype of At runtime we use final class TupleXXL private (es: IArray[Object]) extends Product The issue source of the issue is that The solution is to simply define a proper object Tuple0 extends Product The only reason we have not done this is to be able to use the syntax val tup = 1 *: 2 *: () // equivalent to: (1, 2)
tup match
case x *: y *: () => // equivalent to: case (x, y) =>
case () => // no equivalent (or maybe using a training comma `(,)`) We usually never create or match using |
I wonder if we should use trailing commas to write tuples of size 0 and 1 as |
@nicolasstucki thanks for exhaustive explanation. On the other hand, first thing which comes to my mind is that curly braces usually define a body of code and round braces data, such as tuples. Hence one way out of this might be to reserve
IMHO that should work since it seems to be a valid statement even now. scala> def shrug(params: Any*):Unit = {}
def shrug(params: Any*): Unit
scala> (i:Int) => {i*i;{}}
val res0: Int => Unit = Lambda$15678/0x0000000806430040@403504fe Actually, personally I always write |
Quick grep of dotty codebase yields that there is ~ 1.1k of Moreover there is ~1k of $ grep --include='*.scala' -nri '[^a-zA-Z0-9]()$' . | grep -v '\]()' | wc -l
1154 |
I think that's only doable if we also have an automatic conversion from Tuple0 to Unit, otherwise a lot of code would break (and even with an automatic conversion, some breakage is likely) |
You mean like in the language? That sounds like yet another corner-case-ish feature. Is there going to be a semi-automatic migration script from 2 to 3? If yes than that's the place where such functionality should be, since any usage of |
@smarter Not sure how we would do the pattern matching with a conversion. Could you elaborate? |
Yes, but that's not good enough: we have to support cross-compiling code between Scala 2 and 3, so we can't introduce breaking changes like that.
I have no idea. |
Maybe part of |
Every time we look at this problem we end up realizing the only good way is to have new syntax. We always stop there because |
And now the new syntax change could be keeping only The "only" issue is backwards compatibility for Scala 2, but it would clean up the |
@letalvoj since Unit logically is nothing else but an empty tuple, the would not exactly help anyone... |
There is another big semantic difference between tuples and unit. Tuples have (or should have/mostly have) object semantics while This shows another issue with the current state as |
Other types that are "logically nothing else but an empty tuple" according to the same criteria: all |
We can argue that if that was the case then the current issues should not be happening in the first place. There are two options with the
|
After some offline discussion, we found that we should add the following methods object Tuple {
def apply(): Unit = ...
def unapply(x: Any): Boolean = ...
} then we could write val tup: Tuple = 1 *: 2 *: Tuple()
tup match
case head *: tail =>
case Tuple() => Then we can replace After this, we can also explore the possibility of adding an |
@nicolasstucki I understand why, yet I still think that not using round braces for tuples is a missed opportunity. Pity that preserving backwards compatibility with round braces |
Related issue on [error] 80 | case o:Unit =>
[error] | ^
[error] |this case is unreachable since type Tuple and class BoxedUnit are unrelated yet |
We change to the following hierarchy where all generic tuple types erase to `Product`. ``` Product -- Tuple -+- EmptyTuple | +- NonEmptyTuple -- *:[Head, Tail <: Tuple] ``` These are encoded using the following compile-time only classes ```scalla sealed trait Tuple extends Product { ... } object EmptyTuple extends Tuple { ... } type EmptyTuple = EmptyTuple.type sealed trait NonEmptyTuple extends Tuple { ... } sealed abstract class *:[+H, +T <: Tuple] extends NonEmptyTuple { ... } ``` `TupleN` classes for `1 < N <= 22` are made subtypes of `*:` with precise type (as done before). But `Unit` is not anymore related to `Tuple`. Construction of empty tuples can be done with `Tuple()`, tuples with one element with `Tuple(e)` while any other tuple are created with `(e1, ..., en)`.
We change to the following hierarchy where all generic tuple types erase to `Product`. ``` Product -- Tuple -+- EmptyTuple | +- NonEmptyTuple -- *:[Head, Tail <: Tuple] ``` These are encoded using the following compile-time only classes ```scalla sealed trait Tuple extends Product { ... } object EmptyTuple extends Tuple { ... } type EmptyTuple = EmptyTuple.type sealed trait NonEmptyTuple extends Tuple { ... } sealed abstract class *:[+H, +T <: Tuple] extends NonEmptyTuple { ... } ``` `TupleN` classes for `1 < N <= 22` are made subtypes of `*:` with precise type (as done before). But `Unit` is not anymore related to `Tuple`. Construction of empty tuples can be done with `Tuple()`, tuples with one element with `Tuple(e)` while any other tuple are created with `(e1, ..., en)`.
We change to the following hierarchy where all generic tuple types erase to `Product`. ``` Product -- Tuple -+- EmptyTuple | +- NonEmptyTuple -- *:[Head, Tail <: Tuple] ``` These are encoded using the following compile-time only classes ```scalla sealed trait Tuple extends Product { ... } object EmptyTuple extends Tuple { ... } type EmptyTuple = EmptyTuple.type sealed trait NonEmptyTuple extends Tuple { ... } sealed abstract class *:[+H, +T <: Tuple] extends NonEmptyTuple { ... } ``` `TupleN` classes for `1 < N <= 22` are made subtypes of `*:` with precise type (as done before). But `Unit` is not anymore related to `Tuple`. Construction of empty tuples can be done with `Tuple()`, tuples with one element with `Tuple(e)` while any other tuple are created with `(e1, ..., en)`.
We change to the following hierarchy where all generic tuple types erase to `Product`. ``` Product -- Tuple -+- EmptyTuple | +- NonEmptyTuple -- *:[Head, Tail <: Tuple] ``` These are encoded using the following compile-time only classes ```scalla sealed trait Tuple extends Product { ... } object EmptyTuple extends Tuple { ... } type EmptyTuple = EmptyTuple.type sealed trait NonEmptyTuple extends Tuple { ... } sealed abstract class *:[+H, +T <: Tuple] extends NonEmptyTuple { ... } ``` `TupleN` classes for `1 < N <= 22` are made subtypes of `*:` with precise type (as done before). But `Unit` is not anymore related to `Tuple`. Construction of empty tuples can be done with `Tuple()`, tuples with one element with `Tuple(e)` while any other tuple are created with `(e1, ..., en)`.
Minimized code w Output
Expectation
I should be able to call
($m: Mirror.Product).fromProduct(())
. Currently it's not possible due to the bug above.The text was updated successfully, but these errors were encountered: