microsoft / microsoft/TypeScript
Add pure and immutable keywords to ensure code has no unintended side-effects
まだ誰も着手していません。
- 主要言語
- Go
- スター
- 111k
- フォーク
- 14.4k
- 平均マージ
- 1日 19時間
- マージ済み PR(30日)
- 117
説明
The aim of this proposal is to add some immutability and pure checking into the typescript compiler. The proposal adds two new keywords that would give developers a means to define functions that are pure - meaning that the function has no-side effects, and define variables that are immutable - meaning that they can never be used in an impure context.
Pure
The pure keyword is used to define a function with no side-effects (it is allowed in the same places as the async keyword).
pure function x(arg) {
return arg
}
The keyword should be not be emitted into compiled javascript code.
A pure function:
- May not call an function not tagged as pure in the context of arguments, or this.
this.nonPure(),arg.nonPure(),nonPure(arg), andnonPure(this)are all disallowed.
- May make non-pure calls on instance variables, as the non-pureness applies to instance variables, so the function is technically still side-effect free:
pure function x() {
const arr = []
arr.push(1)
return arr
}
-
- this one i'm not entirely sure about, but it seems like it would be very hard to build pure functions without it.
-
- languages like elm get around this by having a push function which returns a new array.
- Modify the input variables
arg.x = 1is disallowed within the function body.
- Modify variables on
thisthis.y = 1is disallowed within the function body.
- Must return a value (otherwise there's no point to the function!).
Immutable
Similarly a variable may be tagged as immutable:
immutable x = [] (maybe keyword should be shortened to immut, or the pure keyword could be reused for consistency?).
This keyword is replaced with const in emitted code.
An immutable variable:
- Is treated as if it were
const(i.e. its reference may not be reassigned). - May not have any impure instance methods called on it.
x.nonPure()is disallowed.
- May not be passed as an argument to impure functions.
nonPure(x)is disallowed.
- May not be reassigned to a variable reference that is also not marked as pure.
const y = x;is disallowed.const y = { z: x }is disallowed.
- May not have instance properties set on it.
x.foo = 1is disallowed.
- May be passed to pure functions.
pureFn(x)is allowed.
- May have pure instance methods called on it.
x.toString()is allowed.
- For arrays, its type is strictly set at definition time, meaning that element-wise type checks will pass (fixes: https://github.com/Microsoft/TypeScript/issues/16389)
- i.e. the following code will now pass
pure function fn(arg : ('a' | 'b')[]) { }
immutable x = ['a', 'b']
fn(x)
With objects/interfaces
The keyword(s) should also be allowed in object (and by extension interface) definitions:
const obj1 = {
// immutable and non-pure
immutable fn1: function () { },
// mutable and pure
fn2: pure function () { },
// immutable and pure
immutable fn3: pure function () { },
// immutable and non-pure
immutable fn3() { },
// immutable and pure
pure fn3() { },
}
interface IFace {
pure toString() : string // pure must always return a value
immutable prop : number
immutable pure frozenFn() : boolean
}
With existing typings
With this proposal, the base javascript typings could be updated to support it.
I.e. the array interface would become:
interface Array<T> {
pure toString(): string;
pure toLocaleString(): string;
pure concat(...items: T[][]): T[];
pure concat(...items: (T | T[])[]): T[];
pure join(separator?: string): string;
pure indexOf(searchElement: T, fromIndex?: number): number;
pure lastIndexOf(searchElement: T, fromIndex?: number): number;
pure every(callbackfn: (this: void, value: T, index: number, array: T[]) => boolean): boolean;
pure every(callbackfn: (this: void, value: T, index: number, array: T[]) => boolean, thisArg: undefined): boolean;
pure every<Z>(callbackfn: (this: Z, value: T, index: number, array: T[]) => boolean, thisArg: Z): boolean;
pure some(callbackfn: (this: void, value: T, index: number, array: T[]) => boolean): boolean;
pure some(callbackfn: (this: void, value: T, index: number, array: T[]) => boolean, thisArg: undefined): boolean;
pure some<Z>(callbackfn: (this: Z, value: T, index: number, array: T[]) => boolean, thisArg: Z): boolean;
pure forEach(callbackfn: (this: void, value: T, index: number, array: T[]) => void): void;
pure forEach(callbackfn: (this: void, value: T, index: number, array: T[]) => void, thisArg: undefined): void;
pure forEach<Z>(callbackfn: (this: Z, value: T, index: number, array: T[]) => void, thisArg: Z): void;
pure map<U>(this: [T, T, T, T, T], callbackfn: (this: void, value: T, index: number, array: T[]) => U): [U, U, U, U, U];
pure map<U>(this: [T, T, T, T, T], callbackfn: (this: void, value: T, index: number, array: T[]) => U, thisArg: undefined): [U, U, U, U, U];
pure map<Z, U>(this: [T, T, T, T, T], callbackfn: (this: Z, value: T, index: number, array: T[]) => U, thisArg: Z): [U, U, U, U, U];
pure map<U>(this: [T, T, T, T], callbackfn: (this: void, value: T, index: number, array: T[]) => U): [U, U, U, U];
pure map<U>(this: [T, T, T, T], callbackfn: (this: void, value: T, index: number, array: T[]) => U, thisArg: undefined): [U, U, U, U];
pure map<Z, U>(this: [T, T, T, T], callbackfn: (this: Z, value: T, index: number, array: T[]) => U, thisArg: Z): [U, U, U, U];
pure map<U>(this: [T, T, T], callbackfn: (this: void, value: T, index: number, array: T[]) => U): [U, U, U];
pure map<U>(this: [T, T, T], callbackfn: (this: void, value: T, index: number, array: T[]) => U, thisArg: undefined): [U, U, U];
pure map<Z, U>(this: [T, T, T], callbackfn: (this: Z, value: T, index: number, array: T[]) => U, thisArg: Z): [U, U, U];
pure map<U>(this: [T, T], callbackfn: (this: void, value: T, index: number, array: T[]) => U): [U, U];
pure map<U>(this: [T, T], callbackfn: (this: void, value: T, index: number, array: T[]) => U, thisArg: undefined): [U, U];
pure map<Z, U>(this: [T, T], callbackfn: (this: Z, value: T, index: number, array: T[]) => U, thisArg: Z): [U, U];
pure map<U>(callbackfn: (this: void, value: T, index: number, array: T[]) => U): U[];
pure map<U>(callbackfn: (this: void, value: T, index: number, array: T[]) => U, thisArg: undefined): U[];
pure map<Z, U>(callbackfn: (this: Z, value: T, index: number, array: T[]) => U, thisArg: Z): U[];
pure filter(callbackfn: (this: void, value: T, index: number, array: T[]) => any): T[];
pure filter(callbackfn: (this: void, value: T, index: number, array: T[]) => any, thisArg: undefined): T[];
pure filter<Z>(callbackfn: (this: Z, value: T, index: number, array: T[]) => any, thisArg: Z): T[];
pure reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T;
pure reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
pure reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T;
pure reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
push(...items: T[]): number;
pop(): T | undefined;
reverse(): T[];
shift(): T | undefined;
slice(start?: number, end?: number): T[];
sort(compareFn?: (a: T, b: T) => number): this;
splice(start: number, deleteCount?: number): T[];
splice(start: number, deleteCount: number, ...items: T[]): T[];
unshift(...items: T[]): number;
[n: number]: T;
}
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
この提案には、ソースファイル、テスト、エントリーポイントの記載がありません。まず、キーワードの解析、純粋性と不変性のチェック、JavaScript の出力、ライブラリの型定義の更新に関係する TypeScript コンパイラーの領域を洗い出してください。完了とするには、合意された設計、それらの領域全体にわたる実装、提案されたルールと出力結果を対象とするテストが必要です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- javascript, typescript
- 領域
- compilers
- issue の種類
- 機能追加
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 停滞
- 明瞭さ
- 説明が足りない
- 初心者へのやさしさ
- 18/100