I'm trying to narrow down a complex type using a type guard. I would like the false branch of the type guard to understand the complement to the narrowed down type.
interface Model { side: 'left' | 'right'; }
interface LeftModel { side: 'left'; }
interface RightModel { side: 'right'; }
type Either = LeftModel | RightModel;
function isLeft(value: Either): value is LeftModel { // else is RightModel
return value.side === 'left';
}
This does not seem possible, at least not the way I'm going about it. Typescript is happy to infer that an Either
can be a model, but it doesn't accept that a Model
can be an Either
. This is giving an error:
declare const model: Model;
isLeft(model) // ts(2345)
Is this problem fundamentally unsolvable?
If not, how do I get the false branch to narrow down to the complement?
see full example in this Typescript Playground
EDIT
It appears in this naive example that Model
and Either
are equivalent, but this probably cannot be generalised. I could make some progress by combining two type guards, in an attempt to inform the type system that Model
is in fact a valid Either
(see this new Playground). This however leaves me with an unwanted branch (see line 22) so isn't fully satisfactory.
Is there a way to let the type system know that Either
and Model
are strictly equivalent?
I do not particularly need to use either type guards or union types, this was my first attempt at solving the problem but created problems of its own. Union types would only be viable if we can guarantee that the union of a narrowed type and its relative complement are indeed equivalent to the type narrowed down. This relies on the type system having an understanding of complement, which might not be the case (yet). See this typescript complement search and the handbook on utility types
Someone suggesting using fp-ts and/or monocle-ts to solve this but some of these functional programming concepts are still going over my head. If someone knows how to apply them here though that would be nice. Either sounds like it could help here...