1

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)

fjf2002
  • 872
  • 5
  • 15

1 Answers1

2

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.
DoronG
  • 2,576
  • 16
  • 22
  • Thanks a lot, especially for pointing out the `this` handling. However, testing your code with the line `'42'.let(it => alert(it))` works in https://www.typescriptlang.org/play/, but does not work with my tsc` compiler. Both versions are 3.8.3 any idea? – fjf2002 Apr 22 '20 at 09:51
  • Note: I had to write `(Object.prototype as any)` because otherwise my typescript compiler won't accept defining `let` on `Object.prototype`. – fjf2002 Apr 22 '20 at 09:53
  • The error message is `error TS2339: Property 'let' does not exist on type '"42"'.` – fjf2002 Apr 22 '20 at 10:02
  • @fjf2002 if you cast `Object.prototype` to `any` this would not work and will give you the error you're showing. If you're not able to define `let` on the `Object.prototype` it is because you're not merging the `Object` interface. Is this code in a module (does the file have `import` or `export`)? if so, you need to wrap the interface merging with a `declare global` scope. See: https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html – DoronG Apr 22 '20 at 14:26
  • Thanks a lot, the `declare global` thing was essential. The cast to `any` did not matter, as it didn't on the typescript playground example. But `'foo'.let(it => it.toUpperCase())` now has the problem that `it` has type `Object`, not type string. Any idea? – fjf2002 Apr 22 '20 at 16:32
  • There must be some reason why the code does not behave as claimed in https://www.typescriptlang.org/docs/handbook/advanced-types.html#polymorphic-this-types – fjf2002 Apr 22 '20 at 16:57
  • Do you have any explanation or link to the docs why using this-type did not use the subtype, but type-parametrizing with T and using the fake this-parameter `this: T` does !? – fjf2002 Apr 24 '20 at 05:49
  • 1
    @fjf2002 you provided the link above ;) it was just a matter of understanding how to use it right. I'll add explanation to the answer – DoronG Apr 24 '20 at 14:24
  • Thanks again for the explanation :-) – fjf2002 Apr 24 '20 at 15:12