Typescript: Green Functions will save energy!

Created on 18 Sep 2020  Â·  9Comments  Â·  Source: microsoft/TypeScript

TLTR: Put “green” in front of a function definition to enable lazy smart inlining of that function based on it’s parameters values at typescript => javascript compile time.

Suggestion

We want to write easy to understand code and we want code that has no duplicates in it. Whenever we encounter duplicates and repeating patterns we want them to go into a function and then use a function call instead. This makes code easier to understand and better to be maintained.

However if you take a global look at this behavior then you see that it leads to a gigantic amount of overhead being processed and a waste of energy and time as a result. But we could avoid that with a new "green" function type that is fixing that problem.

Use Cases

Every function that is called with fixed parameters at compile time, could benefit. In general every function with a parameter that is tested within the function and the test is known to be true or false for the given input at compile time. Especially parts of frameworks would benefit from this.

Example

starting with the following code in typescript

let parent: HTMLElement = …; //something typescript knows is not null

let div = document.createElement("div");
div.className = "classname";
parent.appendChild(div);

//... many more divs to come

making this easier to read and to maintain we end up with

function create(tag: string, cls: string = null, parent: HTMLElement = null) {
    let ele = document.createElement(tag);

    if (cls !== null) {
        ele.className = cls;
    }

    if (parent !== null) {
        parent.appendChild(ele);
    }

    return ele;
}

let div = create(“div”, “classname”, parent);
//… many more divs to come

so we do a function call for each div and we do !== checks on each function and a return in the end. (not to mention the stuff typescript does on older javascript version to support optional parameter values)

now let’s redefine this function as a “green function” in typescript

green function create(tag: string, cls: string = null, parent: HTMLElement = null) {
    let ele = document.createElement(tag);

    if (cls !== null) {
        ele.className = cls;
    }

    if (parent !== null) {
        parent.appendChild(ele);
    }

    return ele;
}

so nothing changed except a “green” keyword before the function itself. But the javascript generated out of this is different now. First, the function itself has no javascript output. It just does not exist in the output javascript files at all. Instead everytime the function is called it gets inlined into the code as explained below. And this happens in the moment the typescript is compiled to javascript.

//typescript:
let div = create(“div”, “classname”, parent);

//javascript:
let div = document.createElement("div");
div.className = "classname";
parent.appendChild(div);

so we removed the function and inlined the code. The null tests are gone, because we don’t need them.

//typescript:
let div = create(“div”, null, parent);

//javascript:
let div = document.createElement("div");
parent.appendChild(div);

we know cls === null so we know we don’t need to test for it or add a classname.

//typescript:
let div = create(“div”, “classname”);

//javascript:
let div = document.createElement("div");
div.className = "classname";

again parent === null so we don't need to appendChild.

So we:

  • only get the lines of code we wanted in the beginning
  • we don’t execute additional stuff we never wanted to do
  • we keep a easy to read and maintain typescript environment
  • save energy… be green ^^

If you wanna go much more advanced into that. Think about something like jquery with $(“#id”). At the moment you compile this to javascript. You know that you could use a getElementById(“id”) directly without checking the “string” on runtime. So instead you could replace $(“#id”) with $getElementById(“id”) and do exactly that in this function. Surely to do that we need more syntax for that feature, but think about the savings possible in frameworks with such stuff…

But for now a simple “green” keyword with smart inlining of functions based on their parameters would be enough.

Checklist

My suggestion meets these guidelines:

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

Most helpful comment

This would go against the TypeScript Design Goals (whose mention you have removed from the issue template).

All 9 comments

This would go against the TypeScript Design Goals (whose mention you have removed from the issue template).

This would go against the TypeScript Design Goals (whose mention you have removed from the issue template).

I think this is aligned with many design goals of typescript and in no way against them. Can you explain which one it should break and why?

However if you take a global look at this behavior then you see that it leads to a gigantic amount of overhead being processed and a waste of energy and time as a result.

You really gotta explain that statement, as it makes no sense to me... The few null checks definitely don't increase the energy use in any meaningful way. I'd guess that the additional overhead in the compiler and the toolchain would be worse overall.

I think this is aligned with many design goals of typescript and in no way against them. Can you explain which one it should break and why?

Goal 8 Goal 6 (introduction of a new keyword, what if JavaScript suddenly introduces it?), non-goal 5 (no different emit based on the types), and somewhat the non-goal 2(aggressively optimized code is not a goal) and IMO on-goal 7 (the code written is suddenly not the emitted code anymore) as well, at least for me.

Combining the C preprocessor and JavaScript ;-)

You can simply create a d.ts file that declares all macros and then use cpp (or whatever you like) to perform the replacement in the created JS files. This is perhaps not the best way, but there are many ways...

Why does everyone want bugs in Typescript? :D

You really gotta explain that statement, as it makes no sense to me... The few null checks definitely don't increase the energy use in any meaningful way. I'd guess that the additional overhead in the compiler and the toolchain would be worse overall.

This null check example is for sure a very basic and "cheap" one. But it's performed billions of billions of times every day without any reason, because you know it fails or passes in advanced (at compile time). And with compile time i refer to the moment it gets compiled from typescript down to javascript. If you look at the jquery example you see regex being run just to end up at the point you know you would end up before you started the journey. Compiler and Toolchain are a one time run vs. something that gets executed over and over again after.

Goal 8, non-goal 5, and somewhat the non-goal 2 (and IMO on-goal 7 as well, at least for me).

Goal 8: as far as i know as a none english nativ speaker there is still a difference between "avoid" and "never". so i avoided it as much as possible and ended up with a single keyword added to the syntax. i think that passes the goal.

Non Goal 5: i am not talking about any stuff runtime related. i am talking about compile time. so how i would break a goal if i don't relate / interact with it at all?

Non Goal 2: "aggressively optimize" is something else... leaving away lines you never wanted to write in first place, is surely many things but not aggressively optimizing...

Non Goal 7: you expect the function to do exactly what it you read within it's body and you expect that to be executed when you call it. and guess what? it does exactly that. there are simply no surprises at all.

Combining the C preprocessor and JavaScript ;-)

You can simply create a d.ts file that declares all macros and then use cpp (or whatever you like) to perform the replacement in the created JS files. This is perhaps not the best way, but there are many ways...

Why does everyone want bugs in Typescript? :D

any why not have this within typescript itself in a more natural and automated way?

This null check example is for sure a very basic and "cheap" one. But it's performed billions of billions of times every day without any reason, because you know it fails or passes in advanced (at compile time).

Sure, I understand that. But these checks are incredible fast once compiled to machine level, even your jQuery example. And instead of having a single function that gets compiled and cached, you now have even more code that needs to be transferred, parsed and compiled due to the increased output code size (of the various variations). My guess is that this will cost by far more than you will save. The opposite of what you want.

I'm not saying this is definitely the case, but very likely. You would help your cause if you can provide some real metrics, tho they're very difficult to create due to the complexity and all factors involved.

Goal 8: as far as i know as a none english nativ speaker there is still a difference between "avoid" and "never". so i avoided it as much as possible and ended up with a single keyword added to the syntax. i think that passes the goal.

My mistake. I actually meant goal 6. Introducing this new green keyword could lead to incompatibilities with future ECMAScript proposals.

Non Goal 5: i am not talking about any stuff runtime related. i am talking about compile time. so how i would break a goal if i don't relate / interact with it at all?

You have to fully read it: "or emit different code base on the results of the type system." That's exactly what you want.

Non Goal 7: you expect the function to do exactly what it you read within it's body and you expect that to be executed when you call it. and guess what? it does exactly that. there are simply no surprises at all.

It's absolutely not what I expect. Suddenly the emitted code is different. The function I called is completely gone. This also violates the very point of TypeScript to just add static type definitions to JavaScript. With very few exceptions and some legacy decisions the written TypeScript is valid JavaScript when the types got removed.

It's also unclear to me how this would integrate to the rest of language. What if I pass along the function as a argument? Or use the function type with utility types like ReturnType<T>? Function overloads? The easy way out would be to say "forbidden, compiler error", but that would lead to a very poor user experience.

I would expect your proposal to cause issues with Babel as well, which now would need to perform additional tasks besides just removing type information.

any why not have this within typescript itself in a more natural and automated way?

To keep the language focused on its purpose (JavaScript with added static type definitions) instead of suffering from feature creep.


Overall I personally see absolutely no real benefit in this, just added complexity and a huge list of caveats.

Sure, I understand that. But these checks are incredible fast once compiled to machine level, even your jQuery example. And instead of having a single function that gets compiled and cached, you now have even more code that needs to be transferred, parsed and compiled due to the increased output code size (of the various variations). My guess is that this will cost by far more than you will save. The opposite of what you want.

I'm not saying this is definitely the case, but very likely. You would help your cause if you can provide some real metrics, tho they're very difficult to create due to the complexity and all factors involved.

null checks are damn fast, that't true. if they would be any slower we had systems like this in place already for performance reasons. but other checks and test that you could skip are not that fast. regex is surely not fast for example. you can skip all of them if you know the answer to their result in advanced. which you know in so many cases.

sure there is a fight between compile time getting slower the more code you have vs the performance within the runtime that increases. but my goal with this is not primary aimed on performance. it is aimed on a doable way to write exactly the code you want to write. and not being forced into using function and doing checks you never wanted to do in the first place. we only do so because we value readable and maintainable code higher then that. what i try to propose is to keep that readability and that maintainability as it is and still only write the code we wanted to write in the first place.

i don't think metrics would do any benefit here, because then we move into the topic of "aggressively optimize" as a reason. as you said already, it is very hard to tell and depends on the surrounding of the test case.

My mistake. I actually meant goal 6. Introducing this new green keyword could lead to incompatibilities with future ECMAScript proposals.

maybe look at it more as a "type definition" of a function then as a "keyword". you could also achieve this by adding a comment in the function body or do something like "'use green';" there are many options to make sure this would not break anything in the future. there are many valid options here.

You have to fully read it: "or emit different code base on the results of the type system." That's exactly what you want.

"different code base" is referring to a different result and not a equal result. you are not changing any of the result. you are exactly at the same point with and without. that is guarantied. there is no difference, there are no side effects created by this.

It's absolutely not what I expect. Suddenly the emitted code is different. The function I called is completely gone. This also violates the very point of TypeScript to just add static type definitions to JavaScript. With very few exceptions and some legacy decisions the written TypeScript is valid JavaScript when the types got removed.

you expect the code to give you a result. and you get that result. if you don't wanna go that way. you can remove the "type" / "keyword" "green" and you go another way and end up at the same exact spot that you expected to be. you get exactly the result you wanted to get. And if you remove the "green" you still have 100% valid javascript code that gives you the exact same result. you are not breaking anything and you are not violating anything here. just think about it.

It's also unclear to me how this would integrate to the rest of language. What if I pass along the function as a argument? Or use the function type with utility types like ReturnType<T>? Function overloads? The easy way out would be to say "forbidden, compiler error", but that would lead to a very poor user experience.

you would add the "green" type / keyword because you want to use this in exactly that way. so it makes no sense that you wanna do stuff like this. it makes absolut no sense to pass this as a function argument or so. if you want to do that, remove the "green" and go.

even if you need this as a real function, you can go ahead and define the function as a function without a "green". and you have a function with exactly the same behavior. but again, it makes no sense to do so because then you should not define it as "green". it's somehow like creating an number and then trying to use it as a string.

I would expect your proposal to cause issues with Babel as well, which now would need to perform additional tasks besides just removing type information.

just remove "green" and you are at 100% javascript again

Overall I personally see absolutely no real benefit in this, just added complexity and a huge list of caveats.
sad, but okay :) i see more and more use cases, the more i think about it. they pop out of everywhere.

When you are trying to optimize code to be "green" in runtime - please don't forget amount of checks TS should implement that will be triggered on developers devices and CI servers before even getting to users.
Any estimations for saving in comparison with compile time wastes?

Looks like you just want https://developers.google.com/closure/compiler/ for TS syntax, which is mentioned before as "non-goal 2"

Goal 8: as far as i know as a none english nativ speaker there is still a difference between "avoid" and "never". so i avoided it as much as possible and ended up with a single keyword added to the syntax. i think that passes the goal.

This is a fair reading of the design goals, but the design goals should probably say “Do not add expression level syntax.”

Was this page helpful?
0 / 5 - 0 ratings