Currently, I am diving into the world of functional programming using TypeScript for a personal project. My focus lies on harnessing the power of higher-order functions and the pipe function to craft expressive transformers. While experimenting with these concepts, I encountered issues with TypeScript typings, particularly when attempting to design a generic function that produces a transformer from an object type to a subtype of that object.
Let me outline the scenario:
type Cat = {
whiskers: number;
}
type FancyCat = Cat & {
tophatSize: number;
}
type PartyCat = Cat & {
balloons: number;
}
My objective is to devise a function that dynamically generates a transformer to modify any property on any object, ultimately transforming the object into a subtype of itself by adding properties.
While I can achieve this outcome with a hardcoded approach like so:
const createFancyCatTransformer = (props: { tophatSize: number }) => (cat: Cat) => {
return {
...cat,
...props
}
}
const cat = {
whiskers: 50
}
const fancyCat = pipe(cat, createFancyCatTransformer({ tophatSize: 5 }));
I aspire to generalize this process. Instead of utilizing createFancyCatTransformer
, my aim is to implement a versatile function such as
createTransformerToSubtype<Cat, FancyCat>({ tophatSize: 5 })
.
Despite my attempts, I have struggled to make this concept work effectively. As a simplified alternative, I endeavored to develop a transformer capable of extending Cat
to one of its subtypes, only to encounter stumbling blocks.
Here's my endeavor at crafting a generic transformer for Cat:
const createCatExtender =
<E extends Cat>(extension: Omit<E, keyof Cat>) =>
(originalObject: Cat): E => {
return { ...originalObject, ...extension };
};
// Usage
createCatExtender<PartyCat>({ balloons: 7 });
Unfortunately, I faced a TypeScript error:
Type '{ whiskers: number; } & Omit<E, "whiskers">' is not assignable to type 'E'.
'{ whiskers: number; } & Omit<E, "whiskers">' is assignable to the constraint of type 'E', but 'E' could be instantiated with a different subtype of constraint 'Cat'
Absolutely no room for type assertions or similar workarounds are acceptable. Although the solution seems apparent if I assert "as E" in the return statement, my goal remains to circumvent that route.
Is there a way to devise a generic rendition of this function devoid of TypeScript errors? How can I fashion a transformer that universally updates a property on any object type?