2

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)

1 Answer 1

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.
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks a lot, especially for pointing out the this handling. However, testing your code with the line '42'.let(it => alert(it)) works in typescriptlang.org/play, but does not work with my tsc` compiler. Both versions are 3.8.3 any idea?
Note: I had to write (Object.prototype as any) because otherwise my typescript compiler won't accept defining let on Object.prototype.
The error message is error TS2339: Property 'let' does not exist on type '"42"'.
@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: typescriptlang.org/docs/handbook/declaration-files/templates/…
@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
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.