Description
I've spent what seems like an excessive amount of time thinking about intersections. I'm trying to figure why on earth would people assume they are natural or desirable, also considering there are much simpler and even more powerful alternatives with much less "strings" attached. I speculate a pattern of reasoning is that many people assume that if unions are useful and reasonable (which seems to be the case), then naturally, intersections must be as well (after all, aren't they two sides of the "same coin"?).
However, there is a fundamental (and disturbing) difference between the two:
Defining a union type C = A | B
is just like saying that type C
includes all the possible values of A
as well as all the possible values of B
(Val(A) U Val(B)
). It seems to be generally accepted that any two types can be "unioned" this way. This would mean that the universal set or "superset" here is the set of all possible values (or set of all values possibly generated by all possible types). Seeing it like that seems, at least on the surface, reasonable and doesn't seem to have obvious negative implications (at least from the limited perspective of a non-mathematician like myself, but I will leave the possibility that a more knowledgeable person might find issues I'm not capable of).
This is not the same case for intersection!
For type I = A & B
and assuming A
and B
are [edit: non-strict] interfaces (this includes both object or function types), in order for A & B
to have any meaning, Val(A)
and Val(B)
must belong to the same superset! How would you intersect two infinite sets (due to non-strictness) without having a well-defined notion of what values they include?
Now consider A
is an object type and B
is a function type. In order for that to be possible, they would have to be seen as parts of the same superset. Say you "stretch" the math and define the superset as the union of the set of all possible functions and objects [edit: and their hybrids]. Doing that would have some serious implications about assignability:
interface ObjType {
prop: string;
}
interface FuncType {
(arg: number): boolean
}
interface HybridType {
prop: string;
(arg: number): boolean
}
In order for ObjType & FuncType
to have any meaning, the two must belong of the same superset. However, this would imply HybridType
must be considered to be a structural subtype of both ObjType
and FuncType
(remember, all object and function types are currently non-strict in TypeScript). This would be forced by the math:
let obj: ObjType;
let func: FuncType;
let hybrid: HybridType;
obj = hybrid; // This *must* work because it is dictated by the math! Works in 1.5.4
func = hybrid; // This *must* work because it is dictated by the math! Works in 1.5.4
Strangely these assignments are actually allowed today (I assume for legacy reasons, to comply with some Javascript strangeness), but it is not at all obvious to be a natural thing to have. At any case, perhaps the designers decide, some day that some assignments don't make sense and should be outlawed? There is one in particular that I had doubts about:
interface ConstructorType {
new (arg: boolean);
prop: string;
}
let obj: ObjType;
let newable: ConstructorType;
// *Must* be possible if intersections are allowed between objects types
// and objects types with construct signatures! Forced by the math!
obj = newable;
This additional example may not be seen as problematic for the design team (the assignment currently works, but if it's overlooked or controversial, please tell me and I will open a separate issue for it, as that would be off-topic here), and some of these combinations may not even be planned to be allowed in intersections (though multiple inheritance would accept them with no problems or implications whatsoever), but that's not the point.
The point is that by the mere act of introducing intersections (which to some might seem just like a minor, side feature), to some degree, you have sacrificed your type system to the "mercy" of the math gods. You have consciously reduced control over your own type system. Good luck!
[Edit: I had a mistake in the code that tested whether func = hybrid;
works. It does actually work in 1.5.4, I modified the text accordingly]