-
Notifications
You must be signed in to change notification settings - Fork 13
Open
Description
import { optic, optic_ } from "optics-ts";
//this demonstrates the problem with using optics to modify union types (whether using poly or monomorphic optics)
type Manual1 = {
URI: "Manual1";
p1: boolean;
players: number[];
reinforcementsCount: number;
};
type Manual2 = {
URI: "Manual2";
p2: number;
players: number[];
reinforcementsCount: number;
};
type OneOfTheStates = Manual1 | Manual2;
const opticsTest = (input: OneOfTheStates) => {
//first some basic discriminated union sanity checks:
//type "Manual1" | "Manual2" as expected
const uri = input.URI;
if (input.URI == "Manual1") {
input.p1; //discriminated field only on Manual1 works here
}
//now a demo of the problem using the optics
const reinforcementsCount_ = optic_<OneOfTheStates>().prop(
"reinforcementsCount"
);
const reinforcementsCount = optic<OneOfTheStates>().prop(
"reinforcementsCount"
);
//NOTE: Uncomment any of these 3 to get ts error:
// const val_: OneOfTheStates = set(reinforcementsCount_)(3)(input);
// const val: OneOfTheStates = set(reinforcementsCount)(3)(input);
// return modify(reinforcementsCount)(a => 5)(input);
//CULPRIT: Non-Distributive Omit<T,K> used by library breaks discriminated unions
//Since i am using optics modify, the type of modify gives me an `Omit<Type, "propertyModified"> & { propertyModified: ItsType }`
//Omit here doesn't preserve the input type as a discriminated union; it creates a single type where URI is one or the other, and the shared properties are listed,
//but it loses the properties unique to each disrciminated union member.
//because the implementation of Omit, turns out, is not distributive over unions.
type X = Omit<OneOfTheStates, "reinforcementsCount">;
const inputAsX = input as X;
if ((inputAsX.URI == "Manual1")) {
//Uncomment to see unexpected TS ERROR!
// inputAsX.p1;
}
//Since conditional types are distributive for unions (T is a union), we get a distribution of Omit<EachType, K> as a union result
//preserving our ability to typecheck it:
type DistributiveOmit<T, K extends keyof T> = T extends any
? Omit<T, K>
: never;
type X2 = DistributiveOmit<OneOfTheStates, "reinforcementsCount">;
const inputAsX2 = input as X2;
if (inputAsX2.URI == "Manual1") {
inputAsX2.p1; //this works due to the solution of distributed omit
}
SOLUTION: Use a distributive Omit in optics-ts
Metadata
Metadata
Assignees
Labels
No labels