Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest syntactic sugar: extension method/function #45968

Closed
5 tasks done
gregbacchus opened this issue Sep 20, 2021 · 12 comments
Closed
5 tasks done

Suggest syntactic sugar: extension method/function #45968

gregbacchus opened this issue Sep 20, 2021 · 12 comments
Labels
Duplicate An existing issue was already created

Comments

@gregbacchus
Copy link

Suggestion

Syntactic sugar implementation of extension methods/functions without polluting prototypes (or even requiring them at all, i.e. works for interfaces too)

// ./my-extension.ts

export extension addDays(this: Date, days: number): Date {
  const result = new Date(this);
  result.setDate(result.getDate() + days);
  return result;
}

Can be used:

import { addDays } from './my-extension';

const now = new Data();
const nextWeek = now.addDays(7);

Simply compiles to something like

import { getWeek } from './my-extension';

const now = new Data();
const nextWeek = addDays.bind(now)(7); 

So this in the extension can be any type/class/interface and it can be used on anything that matches.

// GIVEN

interface Person {
  name: string;
}

extension getUppercaseName(this: Person): string {
  // or some really useful function :)
  return this.name.toUppercase();
}

// and

const p1: Person = { name: 'Sarah' };
const p2: { name: 'Jane', age: 34 };

// one could

const n1 = p1.getUppercaseName();
const n2 = p2.getUppercaseName()
const n3 = { name: 'Ali' }.getUppercaseName();

🔍 Search Terms

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

  • extension
  • extension method
  • extension function

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Basically it's a simple syntax transformation to make function binding pretty.

Could probably be use for type guards too

interface DataResult {
  kind: 'data';
  data: Buffer;
}

interface ErrorResult {
  kind: 'error';
  error: Error;
}

type Result = DataResult | ErrorResult;

extension isError(this: Result): this is ErrorResult {
  return this.kind === 'error';
}

💻 Use Cases

Any utility library...

@MartinJohns
Copy link
Contributor

MartinJohns commented Sep 20, 2021

Duplicate of #9 (and others). Used search terms: extension methods in:title

[x] This could be implemented without emitting different JS based on the types of the expressions

I really don't see how this could be implemented without emitting different JS.

@gregbacchus
Copy link
Author

I don't believe that it is really a duplicate of #9. May be trying to achieve a similar thing. But it's a very simple implementation.

Maybe I don't quite understand:

This could be implemented without emitting different JS based on the types of the expressions

I don't see how things need to be different based on expression types..?

Are you able to explain?

@MartinJohns
Copy link
Contributor

MartinJohns commented Sep 20, 2021

If you don't think it's a duplicate of #9, then it's definitely still a duplicate of #24889. The very exact thing asked.

And IMO it's a duplicate of #9. You just propose a different syntax, but the feature is the same.

I don't see how things need to be different based on expression types..?

You want different JS emitted depending on the declared extension methods. That's generally a no-go in TypeScript, as it goes beyond the type-system scope TypeScript aims at.

@andrewbranch andrewbranch added the Duplicate An existing issue was already created label Sep 20, 2021
@gregbacchus
Copy link
Author

Thanks for the clarification.

Maybe I didn't explain well. The proposal isn't to emit different JS depending on the types of the expressions. Let me try again:

Extension method declaration

// proposal
extension meExtn(this: MyType): void {
  // ...
}

// treated the same as typescript
function meExtn(this: MyType): void {
  // ...
}

// which emitted in JS as something like this
function myExtn() {
  // ...
}

Extension method usage

  • extension method must be in scope
// proposal
target.myExtn();

// treated the same as typescript
myExtn.bind(target)();

// which emitted in JS as something like this
myExtn.bind(target)();

So what's emitted is not based on the types of the expressions. It's just a simple re-arrangement.

Obviously there would be some fancy type checking stuff that I won't try to pretend to understand.

@MartinJohns
Copy link
Contributor

You're emitting different JS.

@fatcerberus
Copy link

It's still type-directed emit because when TS sees the function call expression x.foo(), whether that should be emitted as x.foo() or foo.call(x) in your proposal depends specifically on the type of x. TS will never do this.

@gregbacchus
Copy link
Author

Oh ok. Thanks for the explanation. I see that now.

I had imagined that if you imported foo, you would always transform. If you don't want the extension, don't import. That makes it very straight forward.

@FilipeBeck
Copy link

I liked this suggestion. I think the philosophy of language shouldn't be that uncompromising. We already have enum and namespace that are outside the rule.

@FilipeBeck
Copy link

I have a lib that extends native types. This feature would fit like a glove.

@MartinJohns
Copy link
Contributor

MartinJohns commented Sep 21, 2021

We already have enum and namespace that are outside the rule.

At least the addition of enum was called a mistake by the team already. And the philosophy exists to protect TypeScript and allow to adopt new ECMAScript features without having to worry about incompatibilities, and to prevent feature creep.

IMO that TypeScript focuses on a single specific goal is something I really like about the language.

@fatcerberus
Copy link

I had imagined that if you imported foo, you would always transform.

Which is still type-directed emit because it relies on the compiler knowing the type of foo, specifically whether it's an extension method or not, information that doesn't exist at runtime.

This is just something that's outside the current design philosophy of the language, which is to be, essentially, "JavaScript with compile-time type checking". With the exception of grandfathered features like enum and namespace, what you write in TS is exactly what runs at runtime in the JS engine, after simply stripping away the types.

Note also that lots of workflows use tsc only for type-checking and do the actual transpilation with Babel, so there's a practical reason to not to things like this too: adding type-directed emit features now would break Babel workflows, as Babel just strips the types and (to my knowledge) doesn't implement any of TS's type system at all.

@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

6 participants