because here ++ can't know if T and S have name clashes. We can't statically check during lookup either:
def lookup[T](r: Record[T with {def age: Int}]) = r.age
lookup(merge(r_b))
we can't know if T contains another def age.
So how should we handle that?
One idea would be not only looking up values by field name, but also by type after that. But TypeTags are expensive and do not have referential equality. The latter may not be a problem. Using Fully Qualified names instead would break with path dependent types.
And how should we handle upcasting in this case?
val r: Record[{def age: Any}] = Record(age=101L) ++ Record(age=99)
r.age
I don't have good answers to these. The best course of action to me right now seems to be deciding on the "best" out of the bad potential behaviors we can think of and well document these.
For example
Alternative 1 (sound, but probably impractical):
allow overloaded fields and use type tags of the expected return types to find which value to return in case of an overload (not sure using the expected return type actually works well for this)
live with the performance hit
Alternative 2 (unsound):
do not use type tags, but something lighter but do the same kind of lookup, e.g. fully qualified names of types and all super types to allow lookup
live with it being broken for path dependent types
Alternative 3 (sound(?), but runtime exceptions in cases that would pass in 2, but also cases that would be just wrong in 2):
always fail for overloaded names at compile times when we can and at runtime when we weren't able to find them at compile time, even if Alternative 2 would have worked in that case
live with runtime exceptions, but at least fail as early as we can
Any better suggestions? Any preferences? I have a slight tendency towards 3. Pushing people to avoid name clashes altogether and fail early and clearly even if at runtime seems better than making them work almost always. If we ever get faster type tags e.g. using scala meta (cc @xeno-by) we may be able to switch to Alternative 1.
so one feature I have in compossible is merging of records
this becomes a problem when field names overlap
which is a valid Scala type. Preventing these cases when implementing
++
as a macro isn't really viable, if this use case should be supported:because here
++
can't know if T and S have name clashes. We can't statically check during lookup either:we can't know if T contains another def age.
So how should we handle that?
One idea would be not only looking up values by field name, but also by type after that. But TypeTags are expensive and do not have referential equality. The latter may not be a problem. Using Fully Qualified names instead would break with path dependent types.
And how should we handle upcasting in this case?
I don't have good answers to these. The best course of action to me right now seems to be deciding on the "best" out of the bad potential behaviors we can think of and well document these.
For example
Alternative 1 (sound, but probably impractical):
Alternative 2 (unsound):
Alternative 3 (sound(?), but runtime exceptions in cases that would pass in 2, but also cases that would be just wrong in 2):
Any better suggestions? Any preferences? I have a slight tendency towards 3. Pushing people to avoid name clashes altogether and fail early and clearly even if at runtime seems better than making them work almost always. If we ever get faster type tags e.g. using scala meta (cc @xeno-by) we may be able to switch to Alternative 1.