case class Foo(x: Int)
var foo = Foo(1)
if someCondition then foo = Foo(2)
If we assume tracked for parameter x (which is implicitly a val), then foo would get inferred type Foo { val x: 1 }, so it could not be reassigned to a value of type Foo { val x: 2 } on the next line.
As far as I understand this stems from the fact the the field refinement is typed as parameter.type, and if the parameter is 1 than we get 1.type, a (precise) Singleton type.
But isn’t this an issue only in case parameter.type is a Singleton like above?
Also, why it’s not possible to widen the type of the field refinement?
I’m asking because not needing a whole new keyword, and just “doing the right thing” by default looks very tempting.
I think the scheme proposed under “modularity” feels really intuitive, it’s just simple substitution, whereas the current scheme seems confusing. (Even I’ve seen some types like ?1 in the past I’ve never understood what this actually is. The refinement on the other hand’s side looks just right; I mean besides the problem that the type and field name can’t be the same in real syntax, but still better then some mysterious ?1).
This problem looks almost like a mirror issue of the question for “precise” / “exact” types btw. Here the type is too specific, there it’s to general.
Wouldn’t there be some way to kill two birds with one stone somehow?
I’ve meant widening the type of the synthetic refinement which gets added by tracked. Not changing widening behavior in general.
Maybe the code snippet showing why tracked can’t be generally assumed for val constructor params is too simple, and I don’t see the actually problem. But in the case of a resulting Singleton type (like 1.type in the example) why can’t the type in the refinement get widened to Int on construction of the synthetic code?
(I suspect that the reason may be the mirror problem to why one can’t narrow down to “exact” types in some cases. But this is just a gut feeling, and I’m not sure what this actually implies. But I thought maybe for someone else some bells will ring… )
That’s again the exact same example as in the docu.
I don’t understand two things:
Firstly, does this only happen with primitive values?
In contrast, say:
class Foo(i: Int)
class C(elem: Foo)
var x = C(Foo(0))
...
x = C(Foo(1))
In that case the type of the C constructor would be C { val elem: Foo }, wouldn’t it? And than the assignment should work, or not?
Or do I completely misunderstand how the feature works?
Secondly, in case of primitive values, why the synthetic refinement can’t be widened?
If the compiler sees that it’s going to construct a refinement like C { val elem: 0 } why can’t it notice that this would be too restrictive (Singleton type == always too restrictive), and just construct instead a refinement of the form C { val elem: Int }?
This would need some special casing in the logic that constructs the refinements, but if it would help to avoid a keyword maybe it’s worth it?
Honest questions. I really would like to see this feature in the best possible form. It seems super useful but it looks like the case that should be default is going to need extra ceremony. Would be really cool if someone figures out a scheme that avoids that.
And in case I just don’t get it, maybe the docu needs some better examples?
Anyway: Thank you so much for looking into all that stuff. It seems Scala is going to get a world class type-class system now. Something I wished for for quite a long. That’s just awesome! I’m super excited!
Thanks! The new example makes it clear that it’s not something related to Singleton types.
But I still don’t get why it needs to be C { val elem: foo0.type } instead of the least specific C { val elem: Foo }? What would break by such a widening?
I think I get it now why this can’t be replaced with C { val elem: Foo }. This would defeat the idea that the type is a path dependent type (the path needs to be a term).
But could something like C { val elem: foo0.asInstaceOf[Foo] } exist? Just virtually in the compiler, as this is not valid code of course.
While .type gives you the most specific type, .asInstaceOf[A] would widen to A (of course only if A is a proper supertype of .type).
Or there could be even some real syntax which gives you the least specific super type of a type. As least specific types are now a thing in type-classes maybe it would be good to have a generic syntax for them? Like .type just the opposite. Say something like .super on a type. So one could have C { val elem: foo0.super }. (Or .type.super, if directly using .super does not work).