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
value classes crippled by nesting restriction #7685
Comments
Imported From: https://issues.scala-lang.org/browse/SI-7685?orig=1 |
@gkossakowski said:
Or in other words, nested value class would need to have outer pointer to preserve semantics of path-dependent types at runtime but then value class would erase to two fields and not one which is not supported at the moment. If we are complaining about value class restrictions, how about: class Foo[T](val x: T) extends AnyVal
class Test[T] {
def test: Foo[T] => Foo[T] = foo => foo
} If you try to compile it you get: Test.scala:4: error: bridge generated for member method apply: (foo: Foo[T])Foo[T] in anonymous class $anonfun
which overrides method apply: (v1: T1)R in trait Function1
clashes with definition of the member itself;
both have erased type (v1: Object)Object
def test: Foo[T] => Foo[T] = foo => foo
^
one error found Which is super confusing error that no regular Scala user can possibly comprehend. However, what Scala compiler hacker can read from it is that if you have a generic value class that erases to object then there's no way you can create a lambda that takes or returns such a class. One could ask why do we even allow generic value classes if a core feature of Scala (lambdas) doesn't work with them and gives a super cryptic error message. |
@paulp said: |
@gkossakowski said: One could imagine that value classes where part of specialization so the specialized (containing erased value class in signature) method would be called I believe that in current form, value classes that erase to object should be disallowed. |
@paulp said (edited on Jul 22, 2013 9:09:25 PM UTC): Here, this has the same problem without value class involvement. The bridge method, were it to be generated, will conflict with the definition of the member itself. class Bippy {
val test: Object => Object = x => x
// Only gets (Object)Object
} But it isn't a problem, because we use the implementation to satisfy the requirement for an (Object)Object method. In all other cases, there is a bridge: class Dingo {
val test: String => String = x => x
// Gets (String)String and (Object)Object
} |
@gkossakowski said: class Foo[T](val x: T) extends AnyVal
class FooFunction extends Function1[Foo[Int], Foo[Int]] {
def apply(foo: Foo[Int]): Foo[Int] = new Foo(foo.x+1)
}
val f: FooFunction = new FooFunction
val foo = new Foo(12) // foo has erased type Int
f.apply(foo) // no boxing here, we should go straight to apply(I)I method
(f: Function1[Foo[Int], Foo[Int]]).apply(foo) // we need to box into Foo before calling apply(LObject)LObject and then that method unboxes and forwards to apply(I)I In this case everything works ok. However, replace Int by Object and you'll see we have a clash, you need two apply's one which assumes you have Foo passed in (and unwraps it) and the other one which assumes you have underlaying content of the method passed in. They have different method bodies but exactly the same signature. That's the problem. |
@paulp said (edited on Jul 22, 2013 9:32:01 PM UTC): def apply(x: Object): Object = if (x instanceof Foo) apply(x.x) else actualImpl(x) |
@paulp said: |
@gkossakowski said: My point is that I'm very skeptical that we can make using generic value classes a smooth experience without any gotchas like we are discussing here. If we cannot make it really good then I'd remove it instead of making people to scratch their heads and hunt bug database for tickets discussing cryptic concepts like bridge methods. |
@gkossakowski said: |
@paulp said: |
@gkossakowski said: |
@paulp said: |
@paulp said (edited on Oct 28, 2013 4:39:16 PM UTC): final class Foo(val value: Int) extends AnyVal
final object Foo {
@inline def apply(x: Int): Foo = new Foo(x)
// Isn't allowed to extend AnyVal due to wrapping value class
// Doesn't do any good not to use an implicit class
@inline implicit final class FooOps(val x1: Foo) {
@inline def bar(x2: Foo): Foo = Foo(x1.value + x2.value)
}
}
class A {
// Super-optimal - implies possible omniscience on part of constant folder
def superoptimal: Int = 5 + 5
// 0: bipush 10
// 2: ireturn
// Optimal - implies only that inlining works
def optimal: Int = (5: Int) + (5: Int)
// 0: iconst_5
// 1: iconst_5
// 2: iadd
// 3: ireturn
def actual: Int = (Foo(5) bar Foo(5)).value
// 0: getstatic #20 // Field Foo$.MODULE$:LFoo$;
// 3: getstatic #20 // Field Foo$.MODULE$:LFoo$;
// 6: astore_1
// 7: astore_2
// 8: new #22 // class Foo$FooOps
// 11: dup
// 12: iconst_5
// 13: invokespecial #26 // Method Foo$FooOps."<init>":(I)V
// 16: getstatic #20 // Field Foo$.MODULE$:LFoo$;
// 19: astore_3
// 20: astore 4
// 22: getstatic #20 // Field Foo$.MODULE$:LFoo$;
// 25: aload 4
// 27: invokevirtual #29 // Method Foo$FooOps.x1:()I
// 30: iconst_5
// 31: iadd
// 32: istore 6
// 34: astore 5
// 36: iload 6
// 38: ireturn
} |
Guillaume Martres (Smarter) said (edited on Apr 3, 2015 11:36:58 PM UTC): case class Foo(val unFoo: Int) extends AnyVal
case class Bar(val unBar: Foo) extends AnyVal
object O {
def thing(x: Any) = {
x match {
case Bar(Foo(i)) => i
case _ => 0
}
}
def test() = {
val bf = new Bar(new Foo(1))
thing(bf)
}
} But this work just fine in my pull request, and it doesn't seem impossible to handle in scalac either. Can someone give me an example of something that is likely to fail with nested value classes? Thanks. |
@lrytz said: I probably won't have time to work more on this right now. |
@SethTisue said: |
@dwijnand said: |
anyone working on this...? |
Noting for posterity, because I tried for this during the development of value classes, and I don't doubt it's still wontfix:
Value classes are much less useful than they could have been. The prohibition on wrapping means that you can take exactly one step down any given value class path and are frozen there forever after. If you try to use value classes in a meaningful way you will run into this restriction constantly. A nontrivial design which is not hampered is not possible.
AFAIK the only reason for the restriction is the requirement that everything be reconstructable at runtime via pattern matching. It is kind of tragic to so severely limit the power of value classes to support typeless-language style programming. If you need to pattern match your way back out of nested value classes, you are Doing It Wrong.
Here is a typical example of where it hits, even before a second API-facing value class appears. Your value class can have non-value extension methods; your non-value class can have value class extension methods; but if you would like your value class to have value class extension methods (and when exactly would you NOT want this for your value class? "Never" is when) you cannot.
The text was updated successfully, but these errors were encountered: