-
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
Mark/infer a function or property as pure/stateless #3882
Comments
#12 covers readonly-ness which would be required for this. Inferring purity is unlikely to ever happen, if it were easily feasible you'd have seen it added to many other static languages by now. As it stands the TypeScript compiler is not really in the business of type directed optimizations/emit so it's unclear how much value this would really add as far as performance and optimizations. Obviously there is some value in the annotations as documentation and contracts but they'd need to be enforced to be valuable. |
Although a keyword modifier for enforcing purity in a whole function may work. Maybe something like this? // Function with side effects
function doSomething(x: number, y: number): void;
imm function add1(x: number, y: number) {
doSomething(x, y); // Error
return x + y;
}
// No error.
imm function add2(x: number, y: number) {
return x + y;
}
// Entire class is designated immutable
imm class Monad<T> {
constructor(public value: T);
fmap<U>(f: (value: T | Monad<T>) => U): U;
}
imm function add3(x: Monad<number>, y: Monad<number>) {
return x.fmap(imm x => y.fmap(imm y => x + y));
} Maybe also, potentially allowing a block to be declared immutable, which can simplify such code. The block would be evaluated as if it didn't exist (no new block scope introduced), but everything in it would be considered immutable (no setting external variables/properties, no calling stateful methods, see notes). function calculateAndPrint(bar: number) {
console.log(doCalculation(bar));
}
imm {
function complexCalculation(x: number, y: number) {
return x + y;
}
function doCalculation(x: number) {
return complexCalculation(x, Math.sqrt(x));
}
} Another thing is that function types should also be able to be required to be immutable, and immutable types considered distinct from mutable ones: type ImmArrayLike<T> = {imm [key: number]: T};
interface Array<T> {
imm map<U>(this: ImmArrayLike<T>, f: imm (value: T, index: number, array: T[]) => U): U[];
map<U>(f: (value: T, index: number, array: T[]) => U): U[];
imm filter(this: ImmArrayLike<T>, f: imm (value: T, index: number, array: T[]) => any): T[];
filter(f: (value: T, index: number, array: T[]) => any): T[];
imm reduce<U>(this: ImmArrayLike<T>, f: imm (acc: U, value: T, index: number, array: T[]) => U): U;
reduce<U>(f: (acc: U, value: T, index: number, array: T[]) => U): U;
imm join(this: {imm [key: number]: T, imm toString(): string}, sep: string): string;
join(sep: string): string;
forEach(f: (value: T, index: number, array: T[]) => any): void;
imm every(this: ImmArrayLike<T>, f: imm (value: T, index: number, array: T[]) => any): boolean;
every(f: (value: T, index: number, array: T[]) => any): boolean;
imm some(this: ImmArrayLike<T>, f: imm (value: T, index: number, array: T[]) => any): boolean;
some(f: (value: T, index: number, array: T[]) => any): boolean;
imm indexOf(this: ImmArrayLike<T>, entry: imm T, start?: number): number;
indexOf(entry: T, start?: number): number;
// ES7
imm includes(this: ImmArrayLike<T>, entry: imm T): number;
includes(entry: T): number;
} Note 1: immutability does not guarantee 0 state within a function. It just means the state is limited to only setting variables local to the function. These should count as immutable: imm function map<T, U>(list: T[], f: imm (value: T) => U): U[] {
const list: U[] = new Array(list.length); // Array constructor is stateless
for (let i = 0; i < list.length; i++) list[i] = f(list[i]);
return list;
}
imm function foo() {
let bar = 1;
function inner() {
bar = 2;
}
inner();
return bar;
} Note 2: implemented class instance properties are implicitly immutable if they are a primitive (i.e. booleans, numbers, strings, symbols, etc.), but getters, interface members, and ambient declarations must be explicitly so. I'm definitely open to criticism here, and I have no real attachment to |
|
|
A pure function is not the same as a static function. Therefore, the |
@mariusschulz I agree, which is why I initially thought of |
I'm skeptical of how well this could work out in practice. A common problem with purity/constness enforcement is that it's "infectious" - once you start trying to make part of your codebase respect this flag, you have to convert everything, which will soon run in to third-party libraries that do wacky stuff (e.g. Knockout, where one overload of a function is pure and one isn't). |
@jbondc there is a reason almost no mainstream languages infer purity, especially those that aren't of the functional heritage with immutability as a default. It is immensely complicated and expensive, and not even desirable for many people. Surely Java, Scala, C#, F# and many others would benefit from this feature as much or more than TypeScript, and some of those languages are far more biased to a pure functional world already. Yet none of them have purity inference (or even explicit purity annotations) despite 10+ years more feature development time than TypeScript has had. There's no shortage of serious academic work in this area and none of it has percolated up into a functioning system in a popular language. |
@danquirk Nit: F# is immutable by default. And Scala doesn't have an explicit default ( |
@jbondc Nit: C# does have some limited type inference, but it's limited to local variable types. C++ looks to be getting more and more type inference capabilities, including inferred |
@IMPinball F#'s let bindings are immutable by default but that does not do anything in terms of purity as phrased here. There're no pure functions as far the compiler is concerned, checking/validation around that fact, or optimizations related to purity. |
BTW, I'd like to see this inferred for implementation functions, explicit in type declarations. Whatever route is chosen, I'd like the ability to override inferred impurity going "this is pure even though it doesn't look like it", in cases like cached computations. |
Tracking at the more popular #7770 |
I think a way to mark and/or infer a function as having no state would be extremely beneficial for code optimization. If a function incurs no state, there are a variety of optimizations that can be applied to it that otherwise couldn't be done. Also, getters and functions that don't modify state can be removed if their results aren't used. Class methods could do the same thing.
Another area where this could be helpful is that if it's explicitly marked, it would be compile-time checked to be fully immutable, i.e. it cannot indirectly modify state. The checks to infer/assert can be independent of
let
/const
, which would make things easier.An explicit marker would also be very useful for type definition files, given some libraries like Lodash and React mostly consist of mutable methods.
The text was updated successfully, but these errors were encountered: