fjf2002
fjf2002

Reputation: 882

Typescript how to augment Object class?

I am familiar with the Kotlin extension function let (and related) (cf. https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/let.html).

I wanted to use something similar in TypeScript, using augmentation (or is it called declaration merging?). So I thought I can add the let method to the Object superclass.

How can I do it? I thought of something like the following, but that didn't work. Typescript doesn't seem to augment, but to solely use the following interface.

interface Object {
  let<T, R>(block: (t: T) => R): R
}

(Object.prototype as any).let = function<T, R>(block: (t: T) => R) {
  return block(this)
}

EDIT:

Test cases would be:

'42'.let(it => alert(it))
'foo'.let(it => it.toUpperCase())

(i. e. let would be available on any objects, in this case strings)

Upvotes: 2

Views: 2518

Answers (1)

DoronG
DoronG

Reputation: 2663

TL;DR

interface Object {
  let<T, R>(this: T, block: (t: T) => R): R;
}

Object.prototype.let = function <T, R>(this: T, block: (t: T) => R): R {
  return block(this);
};

'42'.let(it => alert(it)); // let -> Object.let<string, void>(this: string, block: (t: string) => void): void
(42).let(it => alert(it)); // let -> Object.let<number, void>(this: number, block: (t: number) => void): void
'foo'.let(it => it.toUpperCase()); // let -> Object.let<string, string>(this: string, block: (t: string) => string): string
['foo'].let(it => it[0].toUpperCase()); // let -> Object.let<string[], string>(this: string[], block: (t: string[]) => string): string

codepan

EDIT 1

Updated the above to reflect better typing of the this argument + examples

EDIT 2

Explanation

  1. declaring let on the Object interface merges that declaration with the rest of the Object interface. See Declaration merging
  2. In case this declaration is in a module (a file with import and/or export) you do the declaration in a global scope:
    declare global {
      interface Object {
        ...
      }
    }
    
    See Global augmentation
  3. Using the this argument in the method declaration declares the type of this in the method signature. See Polymorphic thistypes
  4. TypeScript needs a way to implicitly understand the specific type, to do so, we'll use generics. See Generics
  5. Putting all of the above together, by declaring method<T>(this: T), we make TypeScript know that the this argument should take the form of the type the method is executed on. That way, if the method exists on a type (and it does, since we augmented the Object interface), using it on that type causes the this argument to be of that type.

Upvotes: 2

Related Questions