When I do
class A {}
class B extends A {}
const bMap: Map<string, B> = new Map()
const aMap: Map<string, A> = bMap
I get an error saying that A is incompatible with B. The same also seems to apply to arrays. I feel like it ought to be possible to cast in this way, since flow is happy for me to do
const a: A = new B()
as it can recognise that B is a subclass of A and so is a valid operation.
Is this something that just hasn't been implemented yet, or is there a good reason it won't let me do this? I'm aware I could get around it by cloning the Map.
Yes, there is a good reason for this behaviour. This Wikipedia article explains it pretty well.
@bdrobinsonbbc A short reason of why that doesn't work is because Map is mutable. This means that if you can add A elements to the Map from string to A. But an A may or may not be a B, and it would be a type error.
Making your collection type immutable (or covariant) will allow what you're trying to do.
Here's an example:
class A {}
class B extends A {}
const bMap: $ReadOnlyArray<B> = [new B]
const aMap: $ReadOnlyArray<A> = bMap
This has no errors. But if you replace $ReadOnlyArray with the mutable Array you'll get familiar type errors.
Hope that helps.
Thanks for the tip on $ReadOnlyArray, that's very useful. Does Flow have a $ReadOnlyMap for an ES6 Map?
@bdrobinsonbbc I don't think so. Currently ReadOnly versions exist for plain Arrays and Objects.
There are some other issues in Flow itself before it will make sense to make more ReadOnly types.
However, it might be possible to create a ReadOnlyMap type yourself. I'll look into it.
That's good to know. Yeah I wondered the same thing last night and had a go at implementing it myself (taking cues from the implementation of $ReadOnlyArray) - this is what I ended up with. Worth a PR?
declare class $ReadOnlyMap<K, +V> {
@@iterator(): Iterator<[K, V]>;
constructor(iterable: ?Iterable<[K, V]>): void;
entries(): Iterator<[K, V]>;
forEach(callbackfn: (value: V, index: K, map: $ReadOnlyMap<K, V>) => mixed, thisArg?: any): void;
get(key: K): V | void;
has(key: K): boolean;
keys(): Iterator<K>;
size: number;
values(): Iterator<V>;
// Multiple Indexers not yet supported
[key: $SymbolToStringTag | $SymbolSpecies]: Function;
}
declare class Map<K, V> extends $ReadOnlyMap<K, V> {
clear(): void;
delete(key: K): boolean;
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => mixed, thisArg?: any): void;
set(key: K, value: V): Map<K, V>;
}
@bdrobinsonbbc Seems to work. Yup. Worth a PR.
(I feel like K should be covariant too.)
Most helpful comment
@bdrobinsonbbc A short reason of why that doesn't work is because Map is mutable. This means that if you can add
Aelements to the Map from string toA. But an A may or may not be a B, and it would be a type error.Making your collection type immutable (or covariant) will allow what you're trying to do.
Here's an example:
This has no errors. But if you replace
$ReadOnlyArraywith the mutableArrayyou'll get familiar type errors.Hope that helps.