Open nadako opened 6 years ago
This is actually somewhat supported by Map
keys in a hacky way (@:forwardWithAbstracts
for the @:multitType
meta), which could be reworked to RuntimeType
if/when we implement this proposal.
I can understand the idea but this somehow breaks the abstraction of abstracts, since you now have code that depends on their internal representation.
On Sat, Nov 4, 2017 at 5:56 PM, Dan Korostelev notifications@github.com wrote:
This is actually somewhat supported by Map keys in a hacky way ( @:forwardWithAbstracts for the @:multitType meta), which could be reworked to RuntimeType if/when we implement this proposal.
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/HaxeFoundation/haxe/issues/6732#issuecomment-341912382, or mute the thread https://github.com/notifications/unsubscribe-auth/AA-bwFZC4rjct761IW2EnofKZFk6Hb1Mks5szJdQgaJpZM4QSEyC .
Regarding the second issue: you're not really solving the problem with Reflect.fields()
simply being Array<String>
and the compiler not having any reason to assume it's Array<ItemId>
. Sure Array<String>
is Array<RuntimeType<String>>
but while K
is RuntimeType<String>
the converse doesn't hold.
As for the first matter, I can see the appeal. I wonder if something like this might solve your issue:
@:generic
abstract JSObject<K:{ function toString():String; static function ofString(s:String):K; },V>(Dynamic) {
public function new() this = {};
public function get(key:K) return Reflect.field(this, key.toString());
public function keys():Array<K> return [for (f in Reflect.fields(this)) K.ofString(f)];
}
This hinges on @:generic
contraints being allowed static functions and to constrain abstracts to structures even if they don't unify per se (not being objects).
this somehow breaks the abstraction of abstracts, since you now have code that depends on their internal representation
Yes, but that's the idea - know the internal representation from the inside without leaking it (via to T
) from the outside. I think it's useful for generic collections like the JSObject
example of Map
or anything like that.
I think the point is that when you change the internal representation of ItemId
to { id: String }
(for the lack of a better example) any code that uses it as a key for JSObject
will break, because internal details did leak.
well it's the same as with abstract ItemId(String) to String
.
On that we agree. But what I don't get is what the advantage of RuntimeType
would then be?
Allowing to implement runtime-type dependent collections (or other "low-level" parametrized types) without having that to String
leaked just to make it work.
Well, I think the point Nicolas raised is that you're leaking fully equivalent information instead.
In a way having the to String
is better, because then you state explicitly in your public interface that your type is a subtype of String
. It is therefore absolutely clear that if you change that type relationship, you're making a breaking change. Contrast that to RuntimeType<String>
, where outside code asserts such a relationship, thus piercing the abstraction.
Example, why that can be problematic: assume there's some library which has abstract Path(String) { ... }
. The author chose - for the time being - to make it fully opaque of how the type is internally represented. Now you start using Path
as a key into your JSObject
. In the next version of pathlib, the implementation is changed to e.g. abstract Path({ directory:String, basename:String, extension:String }) { ... }
. The author did not change the public API and will release it as a minor change. Your code, however, breaks.
Admittedly, this problem can already be produced with Map<Path, V>
. I'd say that's something to be fixed, rather than expanded on.
Yeah, that makes sense... I guess the "proper" way to handle this without having to String
would be something like type classes (provided by e.g. #6616), but that seems to be a bit overcomplicated.
What I often want is to allow using abstracts over specific type as type parameters without requiring them to be convertible to that type from the outside.
For example, imagine we wanted to provide an alternative to
haxe.DynamicAccess
that would allow abstracts overString
for keys. Obviously we need to constrain the key type parameter toString
, since it's required at run-time:Unfortunately this will not compile according to current rules, because: 1) The
ItemId
abstract is not actually compatible withString
since there's noto String
. 2)Relfect.fields
returnsArray<String>
which is incompatible withArray<K>
due to invariance.Currently there's no way to represent this in type system without losing safety in some way (adding
cast
s and/orto String
to the abstract).To solve this I propose adding a new compiler-handled constraint to
haxe.Constraints
, such as this:Then we could use it as a constraint, e.g.
abstract JSObject<K:RuntimeType<String>,V>
.Compiler would allow unifying
RuntimeType<T>
with any types that are represented asT
at run-time even if they don't define direct casts toT
. Also it would allow unifying type parameters constrained withRuntimeType<T>
with them.