Skip to content

Instantly share code, notes, and snippets.

@ih2502mk
Last active June 8, 2024 00:29
Show Gist options
  • Save ih2502mk/88901249cef02335d6081589330b1fe1 to your computer and use it in GitHub Desktop.
Save ih2502mk/88901249cef02335d6081589330b1fe1 to your computer and use it in GitHub Desktop.
interface LineItem {
id: string,
amount: number,
name: string,
}
type TransformFn<T1, T2> = (item: T1) => T2
// v0 This function takes a set of transform functions and call them in sequence
// someone could say it "composes" them
function createTransform<
T1, T2, T3, T4
>(ts: [
TransformFn<T1, T2>,
TransformFn<T2, T3>,
TransformFn<T3, T4>
]): (item: T1) => T4
// There is a limitation:
// if number of transforms passed to this one as arguments exceeds 3 we'll have an error
// to bypass this limitation we can define this function for some large number of
// arguments and hope that noone needs to compose more than 23 transfroms
// OR
// since the result of createTransform is itself a transform we can tell users that
// if the number does exceed they should split it in half and
// call createTransform on halfs like
// createTransform(createTransform([a,b,c]), createTransform([d,e,f]))
//====
// v1 We can define the function with arbitrary number of parameters but it will have to use any
function createTransform<
T1, T2, T3, T4
>(ts: [
TransformFn<T1, T2>,
TransformFn<T2, T3>,
TransformFn<T3, T4>,
...rest:TransformFn<any, any>[]
]): (item: T1) => any
// Limitation of this approach is that
// if number of transforms exceeds number of properly defined arguments
// we will end up with any
// This is an implementation of createTransform
// valid syntax (repeated funciton defs) due to TS overloading
function createTransform(ts: TransformFn<any, any>[]): (item: any) => any {
return (item) => ts.reduce((transformedItem, tf) => {
return tf(transformedItem)
}, item)
}
// Example transforms
function a(item: LineItem) {
return {...item, addedThing_a: 'aaa'}
}
function b(item: ReturnType<typeof a>) {
return {...item, addedThing_b: 'bbb'}
}
function c(item: ReturnType<typeof b>) {
return {...item, addedThing_c: 'ccc'}
}
function d(item: ReturnType<typeof c>) {
return {...item, addedThing_d: 'ddd'}
}
// 3 args -> everything is typed neatly
const tr3 = createTransform([a, b, c]);
// 4 args -> return type ends up being any :(
const tr4 = createTransform([a, b, c, d]);
const item_abc = tr4({id: '123-qwe', amount: 100, name: 'foo'});
// CAN WE DO BETTER?
// Some thoughts with made up syntax
//// function betterCreateTransform<TypeList>(ts: MakeTransformFn<Pairwise<TypeList>>): (a: TypeList_0): TypeList_Last
// This is not exactly useful
// In our case (i.e. exposing some transform api to developers/consumers of our lib)
// it might be better to derive types the other way around — from transform functions provided by users
//// function evenBetterCreateTransform<TypeList = List<TypesFromArgs>>(ts: List<TransformFn>): (a: TypeList_First): TypeList_Last
// Given list (tuple) of functions we collect types of their argumets and returt type of the last one
// and make return type of transform composition to be a function from first item in a list to last item in a list
// define a generic function that covers all possible functions alternatively could use buit in Function
type GenericFunction = (...args: any) => any;
// jsut check that it works on one of our transforms
type A = typeof a extends GenericFunction ? typeof a : never;
// define an interface that works on our transform funcitons
type TransformList<Fns extends GenericFunction[]> = {
// this technically could be even defined as Fns[0], but
// it would break if we would try to call it against empty array of in place of Fns
// thus
first: Fns extends [infer First, ...infer _] ? First : never,
last: Fns extends [...infer _, infer Last] ? Last : never,
}
type AList = TransformList<[typeof a, typeof b, typeof c]>
type FirstestArg = Parameters<AList['first']>[0]
type LastestArg = ReturnType<AList['last']>
function evenBetterCreateTransform<Fns extends GenericFunction[]>
(fns: Fns): (input: Parameters<TransformList<Fns>['first']>[0]) => ReturnType<TransformList<Fns>['last']> {
return (input) => {
return fns.reduce<ReturnType<TransformList<Fns>['last']>>((trInput, fn) => {
return fn(trInput)
}, input)
}
}
// let's test it out
const evenBetterTransform_ = evenBetterCreateTransform([a,b,c,d])
// typeof evenBetterTransform is (input: never) => never — huh? not what we expected
// The reason is that as far as TransformList type [a,b,c,d] does not extend a tuple
// it's some arbitrary array of functions
const evenBetterTransform = evenBetterCreateTransform([a,b,c,d] as const);
// now types are correct
// Improve the ergonomics i.e. make it look slightly nicer
const _IN: unique symbol = Symbol('IN')
type IN = typeof _IN;
const _OUT: unique symbol = Symbol('OUT')
type OUT = typeof _OUT;
type ExtractEnds<Fns extends GenericFunction[]> = {
[_IN]: Fns[0] extends GenericFunction ? Parameters<Fns[0]>[0] : never,
[_OUT]: Fns extends [...infer _, infer Last] ? Last extends GenericFunction ? ReturnType<Last> : never : never,
}
type BList = ExtractEnds<[typeof a, typeof b, typeof c]>
function bestTransformSoFar<Fns extends GenericFunction[]>(fns: Fns): (input: ExtractEnds<Fns>[IN]) => ExtractEnds<Fns>[OUT] {
return (input: ExtractEnds<Fns>[IN]) => {
return fns.reduce((trInput, fn) => {
return fn(trInput)
}, input)
}
}
const bestTransform = bestTransformSoFar([a,b,c,d] as const)
const aaa = bestTransform({id: '123', amount: 123, name: 'foo'})
console.log(aaa)
// so that `as const` kinda bother me ...
// CAN WE DO EVEN BETTER ???
// What if instead of passing and array we just define bestTransformSoFar as a variadic function?
function totesBestTransform<Fns extends GenericFunction[]>(...fns: Fns): (input: ExtractEnds<Fns>[IN]) => ExtractEnds<Fns>[OUT] {
return (input: ExtractEnds<Fns>[IN]) => {
return fns.reduce((trInput, fn) => {
return fn(trInput)
}, input)
}
}
const totesTransform = totesBestTransform(a,b,c,d)
const bbb = totesTransform({id: '123', amount: 123, name: 'foo'})
console.log(bbb)
@ih2502mk
Copy link
Author

ih2502mk commented Jun 8, 2024

Copy paste this into ts playground to see whats going on...

Inspired by

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment