How to express idempotent (self flatten) types in TypeScript?

Viewed 177

There are types with self flattening nature that is called Idempotence:

enter image description here

https://en.wikipedia.org/wiki/Idempotence

Idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.

In JavaScript/TypeScript, we have Object/Number object for instance of idempotence.

A real world use-case is to write your own Promises with proper type in TypeScript. You can never have Promise<Promise<T>> only ever Promise<T> since promises auto-flatten. The same can happen with monads, for example.

console.log(
 Number(5) === Number(Number(5))
); // true

In a concise way, it's often expressed like

TTX = TX

Edit: in fact, this can be somewhat confusing concept; because Idempotent never has structure more than T, in JS array form [foo]. On the other hand monads operation (bind in Haskel word) is TTX = TX, but it does have structure like [[foo]].

Array.map composition is [[foo]] => [[[foo]]]

Array.flatMap composition map+flat(TTX=TX) is [[foo]] => [[foo]]

this is monad, but not Idempotent, I think. Confusing, yes.

I somehow managed to write in function

const toObject = <A, X>(x: A): A extends T<X> ? A : //...
        ((X:object)=> {/* ... */})(Object(x)) ;

A extends T<X> ? A : //... works in the context of inside of some functions, but I don't know how to write the type itself alone, and even with function structure, it's very complicated, and I feel something is very wrong.

What I want to know and write is a definition of the idempotent type in TypeScript

type T<X> = ???
//where
T<T<X>> === T<X>
1 Answers

You could write an idempotent wrapper around some inner type:

// just for reference, more practically this could be Promise<T>
type InnerType<T> = [T];
type IdempotentWrapper<X> = X extends InnerType<unknown> ? X : InnerType<X>;

type Foo = IdempotentWrapper<number>; // equivalent to InnerType<number>
type Bar = IdempotentWrapper<IdempotentWrapper<number>>; // equivalent to InnerType<number> as well
Related