Stack Tracer
Stack Tracer

Reputation: 1028

How to create a mapping of types?

For example, if I have some code like this for handling distances:

kilo(a: mm): m kilo(a: m): km unkilo(b: km): m unkilo(b: m): mm

And I wanted to expand it to encompass most of the SI prefixes (pico, nano, micro, milli, (normal), kilo, mega, etc...)

That would require a lot of typescript code to do.

Is it possible to create some kind of mapping such that I could just have 2 lines.

kilo (a: T): (lookup T in map) unkilo (b: T): (reverse lookup T in map)

Upvotes: 1

Views: 45

Answers (1)

jcalz
jcalz

Reputation: 330456

I'm not sure how your types are defined, but I will assume they are something like this:

type mm = number & { "**type**": "mm" }
type m = number & { "**type**": "m" }
type km = number & { "**type**": "km" }

That's probably the most lightweight way to represent them: just numbers at runtime. Or maybe you want them to be full-fledged objects.

The important thing is that there is a common property among them (in this case, "**type**") with different string literal values ("mm", "m", and "km"). Then you can do this:

interface KiloMap {
  mm: m
  m: km
}
interface UnkiloMap {
  m: mm
  km: m
}

which represents the mapping you want. TypeScript is good at mapping string literals to general types (that’s what an interface is, really) but not so good at mapping general types to general types (at least not without something like the proposed extended typeof feature).

Finally you can write your function types:

function kilo<T extends (m | mm)>(a: T): KiloMap[T["**type**"]] {
  return a / 1000 as any;
}
function unkilo<T extends (km | m)>(a: T): UnkiloMap[T["**type**"]] {
  return a * 1000 as any;
}

And verify that they work as desired:

const oneMeter = 1 as m;
const oneThousandthOfAKilometer = kilo(oneMeter); // km
const oneThousandMillimeters = unkilo(oneMeter); // mm

You can add new types/mappings/functions as you see fit. Hope that helps; good luck!

Upvotes: 1

Related Questions