-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Static variables in function and method declarations. #8419
Comments
The following works currently: function aFncWithStaticVariable() {
}
namespace aFncWithStaticVariable {
export let foo = 100;
export let bar = "bar";
let privar = true; // Just in case
} |
Function-private static variables are a pattern in JavaScript that is a bit awkward to express in TypeScript. I'd certainly make use of the The dual function/namespace declaration is a bit unweildy and exposes the variables outside the function, whereas function-static vars are usually a private implementation detail of the function. EDIT: Also, the function is hoisted but the namespace isn't, leading to possible ReferenceErrors. I'd like to be able to write something like this: // Given a Foo, compute the corresponding Bar.
function expensiveComputation(foo: Foo): Bar {
// Cache common results to save re-computing them
static let lruCache = new LRUCache<Foo, Bar>();
// If the result is already cached, return it now
if (lruCache.has(foo)) return lruCache.get(foo);
// Compute the result
let bar: Bar = /*** expensive computation ***/
// Add to cache and return
lruCache.set(foo, bar);
return bar;
} Which would downlevel to something like (sans comments): function expensiveComputation(foo) {
expensiveComputation._lruCache = expensiveComputation._lruCache || new LRUCache();
if (expensiveComputation._lruCache.has(foo)) return expensiveComputation._lruCache.get(foo);
let bar: Bar = /*** expensive computation ***/
expensiveComputation._lruCache.set(foo, bar);
return bar;
} |
Why not use the tried-and-true IIFE approach? Capturing the variable in a closure is actually quite idiomatic and has other advantages as well. Or maybe I'm misunderstanding what you're trying to do... |
@aluanhaddad TypeScript uses IIFEs a lot in its downlevelled output (e.g. for namespaces and classes). So things like namespaces and classes are just syntax sugar for what can be equivalently expressed with just IIFEs. But the syntax sugar serves a purpose - it makes the code easier to read and expresses intent more clearly. Function-static variables could just be another syntax sugar with clear intent, that downlevels to IIFEs. |
@yortus indeed, but I don't think that this particular use case warrants an additional concept. It could get quite complicated. For example, if someone tries to use a static variable declaration in a method, what should be the result? Should the method be promoted to a property? Surely not. Should it be shared among instances of the class? Surely not. Should it be an error? |
@aluanhaddad I would restrict this suggestion to function declarations only. As the OP points out, it doesn't make much sense on methods anyway. |
Hoisting is another point against the dual function/namespace declaration approach and the IIFE approach. With hoisting, a function may be called before it is declaration, and that works fine. But it's accompanying namespace won't have been evaluated yet, so referring to any exported variables in the namespace may generate ReferenceErrors. IIFEs aren't hoisted so are not equivalent to function declarations containing static vars as per this suggestion. |
That is indeed true, because namespaces are implemented as IIFEs. However, your example downlevel transpilation example function expensiveComputation(foo) {
expensiveComputation._lruCache = expensiveComputation._lruCache || new LRUCache();
if (expensiveComputation._lruCache.has(foo)) return expensiveComputation._lruCache.get(foo);
let bar: Bar = /*** expensive computation ***/
expensiveComputation._lruCache.set(foo, bar);
return bar;
} exposes the |
Perhaps for you, but I use this pattern from time to time, especially for memoizing and caching. It's useful in functional code.
Only in the same sense that TypeScript exposes the private properties of classes. They don't show up in intellisense and it's a compile error to reference them outside their scope, but if you bypass the type system with some casts to |
Anecdotal, but a quick google search for 'javascript static variable' yields mostly pages asking/describing how to do exactly what's described in this issue. E.g. all from the first page of results:
I don't think this is an esoteric coding practice. |
@yortus one could argue that this practice was highly common when the way to create a "class" in JavaScript up until the syntactic sugar provided with ES6 Classes was to create a constructor function and decorate it with "static" methods and properties and then assign it a prototype. People have been trying to twist JavaScript to match their expectations of a language for years. |
I think the semantics are quite different in most cases. People coming from C++ or even PHP are quite familiar with static variables in functions. They have quite distinct uses from classes.
@kitsonk sure, with the addition of OO class syntax being a particularly successful example of this. |
Quite. And it has often been to the detriment of their code quality and understanding of the language.
As someone with a C++ background, I don't think TypeScript or JavaScript has any obligation to match my expectations from that language. There is an established way to do this kind of caching over successive invocations in JavaScript, and it's one of the things the language does very elegantly. |
@aluanhaddad it's unclear whether you are arguing that this specific suggestion reduces code quality, or you are generally against new syntactic constructs that have some equivalent ES3. Are you also against classes, namespaces, modules, async/await, types, and other 'twists' too? TypeScript provides two forms of productivity: type checking and downlevelling. Are you against the latter in general, or just this specific case? If you could furnish an elegant example of a strongly-typed function declaration that caches its results in a private static variable, whilst remaining hoistable, I'd like to see it. It would advance your argument much better than just saying so, IMHO. |
To be clear, I'm arguing against this specific case. I'm not against down leveling by any means and async/await, modules, arrow functions, and to an extent classes are all features which raise the level of abstraction in meaningful ways. They are also features which are in, or will likely be in, a future version of ECMAScript. I suppose that given the requirement for hoistabiliy, the closest you can get today is your example compiled output function expensiveComputation(foo) {
expensiveComputation._lruCache = expensiveComputation._lruCache || new LRUCache();
if (expensiveComputation._lruCache.has(foo)) return expensiveComputation._lruCache.get(foo);
let bar: Bar = /*** expensive computation ***/
expensiveComputation._lruCache.set(foo, bar);
return bar;
} What I am against is arbitrary, non-orthogonal syntactic extensions that increase the complexity of the language while introducing a number of sharp edges and special cases. This would introduce a local variable declaration form that is only valid within function statements. But really, my subjective argument has nothing to do with down leveling. I just don't think this warrants downleveling, and the argument that it is in language x or language y is not particularly compelling. |
nobody has made this argument. But the OP listed three arguments that have some merit. I think you've mentioned the best argument against this suggestion, which is that it's a syntax extension that does not currently appear to be a JavaScript proposal at any stage. It would be risky to introduce it, as JavaScript might eventually get this feature but with different syntax. I guess @wotsyula you might want to propose this as a syntax extension for ESNext, and if that succeeds one day it will end up in TypeScript. |
I guess I can we can all sacrifice the ability to use functions with static variables before they are defined.
@aluanhaddad By forcing usage after the definition then any form of implementation becomes valid. Also you don't have to include an expression in the function itself to insure the variable is set. I can live with this. It's still better than nothing. I truly understand how difficult it is to implement this in JavaScript. Every solution seems to create 2 or more problems. I agree that the final solution relies in ES @yortus I'm not a fan of es. I feel that the future of JavaScript is in compilation not advancement of features. What we need are better JavaScript compilers. Not a better JavaScript. Look at the C programming language. Very simple and very usefull. But if we need it we can use C# C++ and many more. JavaScript has been around since 1990s without major improvements. Just because a small group of people are getting together to try to change that doesn't mean its gonna happen tomorrow. |
As noted in #8419 (comment), the function+namespace pattern is the recommended approach for defining static variables. |
Although an approach is suggested here #8419 (comment), it seems impossible to use it for instance methods? Correct me if I am wrong. |
@ycmjason this feature wouldn't make sense for instance methods. I argued that it would be confusing since it would be special syntax only valid in function statements and maybe named function expression but neither methods nor |
TypeScript is just sad. |
use var |
@pixelass no u |
🙄 |
This should have been implemented eons ago. I can understand it's unavailability in class methods (You have to ask yourself why you don't just use a static property in the class itself). But it's becoming a pain to have to manually add
funcname.staticvar = ...
after my function definitions.I really need this because:
Parsers written in Typescript. I would like my regular expressions static so I don't have to redeclare them every time I call the function. (ie
HTTP RAW Message -> RequiestObject
/RAW Cookie -> CookieObject
)Prevent me from polluting the global scope. Its better to link variables to the function if they will only be used in the function:
let x = ...;
function y() {
// do stuff with x
}
Code that is easier to understand. The workarounds that I use are difficult for other people to understand.
Thank you for your time. I really hope this can be implemented before version 2. I'm sure a lot of people will be very happy.
TypeScript Version:
1.8.0
Code
Expected behavior:
Actual behavior:
The text was updated successfully, but these errors were encountered: