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

Static variables in function and method declarations. #8419

Closed
wotsyula opened this issue May 2, 2016 · 24 comments
Closed

Static variables in function and method declarations. #8419

wotsyula opened this issue May 2, 2016 · 24 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@wotsyula
Copy link

wotsyula commented May 2, 2016

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:

  1. 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)

  2. 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
    }

  3. 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

function aFncWithStaticVariable() {
    static let foo = 100;
    static let bar = "bar";
    static private let privar = true; // Just in case
}

Expected behavior:

function aFncWithStaticVariable() {
}
aFncWithStaticVariable._foo = 100;
aFncWithStaticVariable._bar = "bar";
aFncWithStaticVariable._privar = true;

Actual behavior:

test.ts(2,2): error TS1184: Modifiers cannot appear here.
test.ts(3,2): error TS1184: Modifiers cannot appear here.
test.ts(4,2): error TS1184: Modifiers cannot appear here.
@ahejlsberg
Copy link
Member

The following works currently:

function aFncWithStaticVariable() {
}
namespace aFncWithStaticVariable {
    export let foo = 100;
    export let bar = "bar";
    let privar = true; // Just in case
}

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label May 2, 2016
@yortus
Copy link
Contributor

yortus commented May 3, 2016

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 static syntax if it was available inside functions.

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;
}

@aluanhaddad
Copy link
Contributor

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...

@yortus
Copy link
Contributor

yortus commented May 3, 2016

@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.

@aluanhaddad
Copy link
Contributor

@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?

@yortus
Copy link
Contributor

yortus commented May 3, 2016

@aluanhaddad I would restrict this suggestion to function declarations only. As the OP points out, it doesn't make much sense on methods anyway.

@yortus
Copy link
Contributor

yortus commented May 3, 2016

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.

@aluanhaddad
Copy link
Contributor

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 static, cached property. Also, as you implied, the approach only works with function statements, potentially named function expressions could work as well. It won't work with either => functions or anonymous function expressions. It feels overly specific.

@yortus
Copy link
Contributor

yortus commented May 3, 2016

It feels overly specific.

Perhaps for you, but I use this pattern from time to time, especially for memoizing and caching. It's useful in functional code.

your example downlevel transpilation example [...] exposes the static, cached property

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 any you can access them, yes.

@yortus
Copy link
Contributor

yortus commented May 3, 2016

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.

@kitsonk
Copy link
Contributor

kitsonk commented May 3, 2016

@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.

@yortus
Copy link
Contributor

yortus commented May 3, 2016

one could argue that this practice was highly common when the way to create a "class" in JavaScript

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.

People have been trying to twist JavaScript to match their expectations of a language for years.

@kitsonk sure, with the addition of OO class syntax being a particularly successful example of this.

@aluanhaddad
Copy link
Contributor

People have been trying to twist JavaScript to match their expectations of a language for years.

Quite. And it has often been to the detriment of their code quality and understanding of the language.

People coming from C++ or even PHP are quite familiar with static variables in functions.

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.

@yortus
Copy link
Contributor

yortus commented May 3, 2016

@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.

@aluanhaddad
Copy link
Contributor

aluanhaddad commented May 3, 2016

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?

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.

@yortus
Copy link
Contributor

yortus commented May 3, 2016

the argument that it 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.

@wotsyula
Copy link
Author

wotsyula commented May 3, 2016

I guess I can we can all sacrifice the ability to use functions with static variables before they are defined.

foo(); // Error Function used before defenition

function foo() {
    static x = 1;
}

foo(); // Okay

@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.

@mhegazy
Copy link
Contributor

mhegazy commented May 20, 2016

As noted in #8419 (comment), the function+namespace pattern is the recommended approach for defining static variables.

@mhegazy mhegazy closed this as completed May 20, 2016
@ycmjason
Copy link

Although an approach is suggested here #8419 (comment), it seems impossible to use it for instance methods? Correct me if I am wrong.

@aluanhaddad
Copy link
Contributor

@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 => functions would make any sense.

@pixelass
Copy link

pixelass commented May 30, 2018

TypeScript is just sad.

@isc30
Copy link

isc30 commented Jun 25, 2018

use var

@anurbol
Copy link

anurbol commented Sep 15, 2018

@pixelass no u

@RyanCavanaugh
Copy link
Member

🙄

@microsoft microsoft locked and limited conversation to collaborators Sep 16, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests