Open zpdDG4gta8XKpMCd opened 8 years ago
We need more information about what you would expect this to do
A pure function does still not guarantee that a callback is invoked immediately:
function wrap(f: () => void) {
return { f };
}
let x = Math.random() > 0.5 ? 'hey' : 1;
let obj: { f: () => void };
if (typeof x === 'number') {
obj = wrap(() => x + 2);
}
x = '';
obj.f();
In my opinion, a better idea would be to narrow constant variables only in callbacks, as that would be sound.
I'd then say that only the callback has to be pure. In your example, narrowing could even happen when map
is not pure, as it cannot modify the value of x
(and if it would, the callback wouldn't be pure at all).
This would be boon to productivity, and would afford developers a practical tool to isolate side effects
const
declaration + readonly
modifier give us this:
no variables defined in the outside scope that are proven to possibly mutate can be used in a pure function
@edevine, i would say it means
"const declaration + readonly modifier" for all sub-objects all the way down to the last primitives
For context we have discussed similar proposals as part of the readonly modifier support. see https://github.com/Microsoft/TypeScript/issues/6614 for more information.
related #8381
A pure function does still not guarantee that a callback is invoked immediately:
Little bit OT, but I would like if TS can add an immediate
modifier also to resolve the above problem:
declare interface Array<a> {
map<b>(map: immediate (value: a) => b): b[]:
}
let x = Math.random() > 0.5 ? 'hey' : 1;
if (typeof x === 'number') {
[].map(() => x + 2); // no error callback is called immediately
}
function f(f: immediate () => void) {
return { f }; // error callback must be called.
}
function f(f: immediate () => void) {
return fs.readFileAsync(f); // error cannot call immediate callback async.
}
@aleksey-bykov
'Pure' function means (wikipedia):
- The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices (usually—see below).
- Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices (usually—see below).
In order to satisfy (1.) this would mean it would also exclude read operations to any captured entity and global scope entity (window
, document
, etc.). The only remotely possible exceptions here are captured constants with a primitive type like number
, string
, boolean
etc., however even they can technically have properties, so even if property access is prevented on them, they could still be returned from the function, and potentially the caller would receive a different return value between calls. This means the function couldn't basically read or write from/to anything outside of it, including captured variables (or possibly constants) from another pure function, as those are not guaranteed to have the same value at each execution.
It may be that the analysis is actually easier than the one that's needed for #8353, but I'm not sure.. maybe it's safe to simply say it's 'different'.
Maybe the intention here wasn't really for 'pure' functions in the conventional sense, but a form of a non-side-effecting function, that could still return different values at each execution but is 'guaranteed' not to silently influence the state (including, say things like I/O). That would be closer to the analysis needed for #8353, but would include more components like modification of properties (which isn't really included there, it is only about reassignments), having an understanding of I/O related operations etc.
I can see possible side effects from getters being a problem, so this would mean that there should be some way to detect regular interface properties (not methods) that could still have side effects, so using readonly
wouldn't be sufficient here:
interface MyInterface {
readonly prop: number;
}
class MyClass implements MyInterface {
get prop(): number {
mutateGlobalState();
return 1;
}
}
nonmutating function imSupposedToHaveNoSideEffects(arg: MyInterface) {
let num = arg.prop;
}
imSupposedToHaveNoSideEffects(new MyClass())
It needs to be something like:
interface MyInterface {
nonmutating readonly prop: number;
}
(I'm using the nonmutating
modifier here temporarily, just for illustration, perhaps there's a better one for this)
[Edit: Modified the code example to make it a bit clearer]
[Edit: Or maybe the whole interface or class should be tagged as nonmutating
?]
this is what 3 is about: anything closed over by a pure function has to be immutable, or else being pure should not typecheck On May 1, 2016 5:14 AM, "malibuzios" notifications@github.com wrote:
I can see possible side effects from getters being a problem, so this would mean that there should be some way to detect readonly interface properties that could still have side effects, so using readonly wouldn't be sufficient here:
interface MyInterface { readonly prop: number; } class MyClass implements MyInterface{ get prop(): number { mutateGlobalState(); return 1; } }
nonmutating function imSupposedToHaveNoSideEffects() { let x = new MyClass(); let num = x.prop; }
It needs to be something like:
interface MyInterface { nonmutating readonly prop: number; }
(I'm using the nonmutating modifier here temporarily, just for illustration, perhaps there's a better one for this)
— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/Microsoft/TypeScript/issues/7770#issuecomment-216029209
@aleksey-bykov
I'm sorry, I might have misunderstood, the intention wasn't very clear from the way it was described. You are right that other 'pure' functions can be called from 'pure' functions (I didn't mention function calls). But variables, properties or even whole classes and interfaces would need to somehow be 'deeply' immutable as well, otherwise it wouldn't really work. const
and readonly
don't guarantee immutability of properties.
const globalVar = { prop: 1 };
pure function func(): { prop: number } {
return globalVar;
}
func(); // result is { prop: 1 }
globalVar.prop = 2;
func(); // result is { prop: 2 }
So what you mean is that there should be a 'deeper' form of immutablity, which also includes properties. This would either require something like a keyword or an immutable
type trait for variables and members. I still don't have a great idea on how to model this, especially with anonymous object literals, maybe:
immutable globalVar = { prop: 1 };
globalVar.prop = 2; // error
Or a type trait:
const globalVar = <immutable> { prop: 1 }; // type of globalVar is 'immutable { prop: number }'
globalVar.prop = 2; // error
I also considered the fact that although strange, in Javascript even primitives may have properties, and const
doesn't prevent these to be modified:
const x: number = 1;
x["secret"] = 123; // no error
[I've tested this in both Firefox and Chrome and it doesn't error, however the resulting property value is undefined
, same for let
, so I might open an issue for this. I still need to check if this happens in all cases and what the standard says about this, though, both in strict and non-strict mode]
Anyway, both pure
(in the conventional sense) and nonmutating
are useful ways to model different kinds of scenarios, so I'm also exploring the 'weaker' nonmutating
variation (that although less 'safe' is more useful in practice) as well in the context of a (currently 'hypothetical') programming language I'm thinking about, but it may be interesting to share here, so bear with me:
A further question that needs to be considered is whether a non-mutating function should still be allowed to mutate external state through one of its arguments:
nonmutating function func(obj: { prop: number }) {
obj.prop = 2;
}
var globalVar = { prop: 1 };
function example() {
func(globalVar);
}
I believe this is may be unavoidable, so the answer would have to be 'yes' (this also means that nonmutating
wouldn't be an ideally precise name for the modifier, since it can still mutate through arguments).
Trying to detect the passing of captured entities wouldn't really help here:
var globalVar = { prop: 1 };
function example() {
let x = { a: globalVar };
func(x.a); // This would be very difficult to reliably detect..
}
I was thinking of this in the context of ideas for a hypothetical programming language, that is still imperative and 'impure' but has strong safeguards when it comes to side-effects. Having this 'middle ground', where functions cannot have 'silent' side-effects but could still modify global state through arguments seemed like an interesting compromise.
However, this may become uncomfortable to the programmer, say, to have to pass the print
function for any function that may print something to the display:
enclosed function iPrintStuff(printFunction: (message: string) => void, text: string) {
printFunction(text);
}
iPrintStuff(console.log, "Hi");
One way of mitigating this (mostly for this 'hypothetical' language, but perhaps also relevant here) would be using a rather different way to organize how the program interacts with state. Perhaps the general pattern would be to use what I call 'enclosed' classes instead of functions, where the class would receive all the external entities that it would need for its internal operations during construction, but otherwise cannot silently influence external state.
enclosed class PrintHelper {
constructor(private printFunction: (message: string) => void) {
}
print(message: string) {
this.printFunction(message);
}
}
let printHelper = new PrintHelper(console.log)
printHelper.print("hi");
This may seem somewhat strange or unneccsary, but it does provide a 'controlled' way to guarantee several properties that may be important from a design perspective, although there are probably more 'elegant' ways to model this, but would require some further syntax. One that I can think of is having a special rule that static constructors can reference captured variables as well as mutating functions like print
:
declare var globalVar;
enclosed class PrintHelper {
static private prop;
static private printFunction: (message: string) => void
static constructor() {
// These assignments are only allowed in the constructor:
this.prop = globalVar;
this.printFunction = console.log;
}
static print(message: string) {
this.printFunction(message);
}
}
PrintHelper.print("hi");
(I'm still in an early state of developing this..)
I think this may be better syntax. Here, the compiler can easily become aware and analyze what external state the class can possibly 'touch' (the foreign
deceleration would be required in order to reference an external entity in the body of the class):
var globalVar = 1;
enclosed class PrintHelper {
foreign globalVar; // This just captures 'globalVar' above,
foreign log = console.log; // This provides a local alias to 'console.log',
static updateGlobalVar() {
globalVar = 2; // foreign references don't require 'this'
}
static print(message: string) {
log(message);
}
}
PrintHelper.print("hi");
My intention is that foreign
s can also possibly be set on construction, but I'm still working on how to do it elegantly.
A stronger version of this could also disallow all members of silently reading non-immutable external entities, so they would be closer to real 'pure' functions: Having no foreign
members would essentially mean all of its methods are almost pure, as they cannot have any side effects outside the boundaries of the class. Having no properties - only methods - would mean they are 'truly' pure, in the sense of providing referential transparency.
(By saying this I do assume here that these methods will only accept immutable arguments, however that restriction can also be weakened, so there's a range of possibilities here).
Edit: Conceptually this seems somewhat like a hybrid between an isolated 'module' and a class. Sort of like an 'instantiable module' - when the class is not completely static, I mean.
Edit: Reworked the example a bit for a more compact syntax.
The more I look at it, I start to feel that what I'm really 'looking' for here may be better expressed (at least in TypeScript) as an 'enclosed namespace', rather than a class, which would have no access to non-immutable outside state (including the standard library and DOM) unless an explicit foreign
declaration is used:
var globalVar = 1;
enclosed namespace Example {
export function updateGlobalVar(value: number) {
foreign globalVar; // This just captures 'globalVar' above
globalVar = value;
}
export function print(message: string) {
foreign log = console.log; // This provides a local alias to 'console.log'
log(message);
}
export function pureFunction(x: number) {
return x + 1;
}
}
The main problem here is convenience: how to avoid requiring the programmer to write many foreign
declarations for each little thing they need from the outside context..
Edit: I've decided to experiment with having the foreign
declarations scoped just like type
declarations and move them as close as possible to the position they are actually used (they essentially only serve to tell the compiler to 'import' something from outside of the 'fenced' namespace and are erased in the resulting compiled code).
Update: I've completely 'rebooted' the whole thing and started from scratch using a different approach, which is conceptually closer to what was originally proposed but concentrates on the weaker, but more useful 'spectator-only' mode rather than trying to achieve referential transparency. However, it will take some time to work out all the details, perhaps up several weeks to get this to something approaching a real 'full' proposal.
The 'reader' modifier is a way to tell the compiler that a particular function, method, class or interface member does not induce any side-effects outside of its own internal scope. This is not, however, a sufficient condition to characterize the affected entity as 'pure' in the same sense that a 'pure' function would be, as it does not guarantee referential transparency, that is, the property that for a given input, the same output would be returned at all subsequent calls. It can be seen as a 'middle-ground' between the 'extreme' mutability of imperative languages and the extreme 'purity', or, non-mutability trait in some functional languages.
The purpose and usefulness of having this is both for programmers, to be able to create better and safer contracts, for themselves and for others, and compilers, to better reason about the code and become more closely aware of the intention of the programmer.
A reader
function:
readonly
or const
.reader
functions or methods.reader
classes.reader
members of an external class instance or interface (this may be implicitly detected, see the 'open questions' section below).A reader
function or class constructor arguments may only include:
reader
functions.reader
members.Examples:
This wouldn't work (it guarantees 'no side effects')
var mutableVar = 1;
const obj = { a: "hi" };
let func = () => { mutableVar = 3 };
reader function doSomething() {
mutableVar = 2; // Error
obj.a = "bye"; // Error
func(); // Error
}
doSomething();
But this would (no special guarantee for referential transparency):
var mutableVar = 1;
reader function doSomething(): number {
let x = mutableVar; // this assignment is by value, so this would work
x += 1;
return x;
}
doSomething(); // returns 2;
mutableVar++;
doSomething(); // returns 3;
A class annotated as a reader
:
reader
function.reader
function, it can internally call reader
functions, instantiate reader
classes, and use reader
members of an interface.A class method annotated as a reader
(which may be enclosed both in a reader
or non-reader
class), is very similar to a reader
function.
reader
functions.A non-reader
method in a reader
class:
A reader
interface member:
reader
function or class method.Examples:
interface DOM {
reader getNode(path: string): DOMNode
setNode(path: string, value: DOMNode)
// ...
}
declare var dom: DOM;
declare reader class Dictionary<T> {
reader lookup(key: string): T;
reader clone(): Dictionary<T>;
add(key: string, value: T);
// ...
}
reader function getNodes(paths: string[]): List<DOMNode> {
let result = new Dictionary<DOMNode>(); // It is only possible to instantiate this class
// here because it is a 'reader' class.
for (let path of paths) {
let node = dom.getNode(path); // Despite the fact that DOM is not 'purely' a
// 'reader' interface (as it changes the internal state of
// the browser), 'getNode' is a reader, so it is guranteed
// not to modify any state, including the state of any
// external class that may lie 'beneath' its interface
result.add(path, node); // The 'add' operation is destructive, however, since 'Dictionary'
// is a 'reader' class the effect would be only local to this
// function, so this is allowed.
}
return result;
}
reader
s as well?reader
functions or methods.reader
class is passed to a reader
function, should the function be allowed to call the non-reader
methods of that class? (doing that could indirectly mutate external state, but only through that particular class instance)reader
class that can only mutate its own class instance?reader
function, can they be non-readers but still bound by the scope of the enclosing function, just like in classes?reader
function? should they be 'reduced' to their reader members as well? maybe this would happen implicitly but only inside of a reader
scope?reader
? or maybe a different name for the class modifier?Since interface properties, (i.e. not methods) can have getters behind them, and these getters technically may have side effects, should they be required to be marked as readers as well?
I hadn't considered this. https://github.com/jonaskello/tslint-immutable/issues/41
When will this be implemented?
pure
modifierI will suggest using the name pure
instead of using reader
to have more clarification.
For example :
var mutableVar = 1;
const obj = { a: "hi" };
let func = () => { mutableVar = 3 };
pure function doSomething() {
mutableVar = 2; // Error
obj.a = "bye"; // Error
func(); // Error
}
doSomething();
So the metaphor goes like this :
pure
, then it shall be pure (no side effects) or else the compiler will throw errorpure
function can only call pure
function. pure
should be an error even though the function does not bring any side effects.
function add(x: int, y: int){
return x + y;
}
pure function getMyName(){ return "Spongebob"; }
pure function doSomething() { var x = add(1, 2); // Error var s = getMyName(); // No error }
## Regarding I/O operations
* Since `pure` function should have no side effects, it is also reasonable that a `pure` function should not call any I/O functions.
* Actually, it is also due to the reason that I/O functions are not possibly marked as `pure`
* For example :
```ts
pure function sayCheese(){
console.log("Cheese!") //Error
}
* Classes cannot be marked as pure
as this violates the whole purpose of object-oriented paradigm, as classes are meant to store state.
readonly
pure
For example, the following is a valid pure class:
pure class Color {
public readonly red : number;
public readonly green: number;
public readonly blue : number;
public constructor(red:number, green:number, blue:number) {
this.red = red;
this.green = green;
this.blue = blue;
}
public pure darker(factor = 0.25): Color {
return new Color(
this.red * factor,
this.green * factor,
this.blue * factor
);
}
}
Object.prototype
, it shouldn't be a problem, because all of them are pure
. Take a look at list of Javascript's Object.prototype methods.class Example {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
pure sayHello() {
this.greeting += "!!!!"; // Error as pure method cannot modify variable outside of own scope
return this.greeting; // No error
}
}
pure
.pure
and vice versa. For example :
class Starfish {
name: string;
constructor(name: string) {
this.name = name;
}
shout() {
return "Hey I'm Patrick";
}
pure jump() {
return "How can I jump?";
}
}
class Sponge { friend: StarFish; constructor(friend: Patrick){ this.friend = friend; }
pure play(){
friend.shout(); // Error because shout() is not pure
friend.jump(); // No error as jump() is pure
}
}
* Based on the rule above, a function is also allowed to call a pure method of a class.
## Final words
If you have a C++ experience, you'll notice that `pure` is actually the [`const` modifier](http://www.geeksforgeeks.org/const-member-functions-c/). For example, a pure function/method in C++ would be declare as such:
```cpp
class Example {
private:
string name;
public:
string GetName() const {
return name;
}
}
As a good practice, methods/function should be marked as pure
whenever possible so that the process of debugging will be as smooth as jelly.
If this pure
modifier is implemented, the TypeScript linter TSLint should suggest coder to mark any function that does not have side effect as pure
.
pure
class. (2018-05-15)Why clean instead of pure? Pure seems to be the accepted jargon in functional programming for having no side effects.
Other than that it's looking nice
@lastmjs Changed to pure
already.
@wongjiahau Immutable objets (strings, numbers, ...) and then "pure" classes make sense...
@wongjiahau
I love your proposal, but I do have a serious issue with this:
Classes cannot be marked as pure as this violates the whole purpose of object-oriented paradigm, as classes are meant to store state.
It's fairly contentious to say that's the entire point of OO. I mean there are multiple definitions, it's not like there's a set of commandments saying objects are this, or objects are that. More concretely - immutable objects are a very common design pattern. If every field in the class is readonly and every method is pure, it makes sense to me to say that the object is pure.
@wongjiahau In agreement with @LewisAndrewCampbell, Scala serves as an example, as it has its record types (known as "Case classes"), which are often treated as immutable (and, often, pure).
As with most languages' record types, to "change" these objects, you use a special syntactic construct to clone them while altering only the properties you explicitly specify.
As for purity opposing OO- Scala is probably one of the more strict OO languages I've seen- it doesn't include static
for example, as even the static instance is a class (referred to as a "Companion Object"). Many developers use the language as if purity were enforced- and the language team is in the midst of designing an "Effects System" which allows enforced purity.
@LewisAndrewCampbell @Dessix I had updated the proposal based on your comments.
If TS can detect error in pure function, it can infer state on function, pure or not. So, I think pure
modified could be avoided.
@Strate explicit modifiers like pure
is what holds the code from falling apart
imagine you chose to deduce them automatically, and in an ideal situation everything compiles... then you broke something that rendered one function impure... and got 4236 cascading errors because of one place that is not pure anymore, now go figure what went wrong, i guarantee you will be looking for a needle in a stack of hay
instead if you chose to go with explicit modifiers the error would be localized within a few files
Is this a dupe of #3882?
@isiahmeadows I would say this is an incarnation of #3882 as this threads had more ideas and activities.
Just came upon this topic because I liked the way that D implements pure
functions and would love to have similar abilities in JavaScript. Does anyone know if this topic is still discussed for TypeScript? Or how to best progress to get it on the roadmap?
Any movement on this? It fits nicely with TypeScript's design goal of leaving no footprints on runtime code, unlike immutable.js, the current standard. Preventing side-effects statically would be a minor revolution for the JavaScript ecosystem I think.
What about const
keyword in place of pure
? pure
is a valid identifier in JavaScript while const
isn't.
const
already has a meaning in JS and TS, so that would break it. pure
is a valid JS identifier, but so are number
, string
, etc. TS necessarily has to reserve more keywords than JS to do its job.
Perhaps instead of a fix modifier something that allow us to control what can be done inside a function, it will allow more things like a function that can run only some small set of functions.
Or at least some "official statement" would be cool, even if is "if someone is willing to contribute feel free"
Any news on this issue? Would love to have this kind of types in TypeScript!
Are there any news? This is one of those things that could really improve the code quality in bigger projects.
Any news?
Related stack overflow questions:
This is the one thing I really want to see next from typescript.
Many good points already in this thread, but I have a few more to add. To implement it, I would start with a simple but restricted version:
The implementation described above is limited, but it's a good start imo.
Some issues, with potential solutions for more advanced support:
Defining new functions inside a pure function to operate on local state:
pure function test(arr){
const acc = {}
function addToAcc(key, val){
acc[key] = val
}
for(let x of arr){
addToAcc(x.key, x.val) // ERROR: addToAcc is not pure (but that's actually ok)
}
return acc
}
A possible solution here is to treat every non-pure function defined inside a pure function as part of the pure function. Check it as if it is the parent function. To be more specific: find the closest pure parent, and treat that as the scope for purity check.
Calling non-pure functions that only modify variables local to the function:
pure function test(){
const a = []
a.push(5) // ERROR: push is not pure (but that's actually ok)
return a
}
In this case, "push" has a side effect of modifying the content of variable a. Or said differently, it mutates "this", which can be thought of as an input parameter (like "self" in python). Since the parameter lives in the scope of the pure function, it should be a legal mutation. I guess figuring out the lifetime of "this" is the key here. Also, it might be necessary to somehow indicate that a function ONLY mutates input parameters, but does not have any other side effects.
Edit: More cases:
Using reduce:
pure function sum(arr){
return arr.reduce(pure (a, b) => a + b, 0) // No error. Reduce accepts a pure function. The accumulator cannot possibly be mutated.
}
pure function test(arr){
return arr.reduce((a, b) => {
a[b.key] = b.val
return a
}, {}) // Should be ok. Reduce accepts a non-pure function, but operates on a local object. Since the function is defined here, treat it the same as the parent pure function.
}
function objSet(a, b){
a[b.key] = b.val
return a
}
pure function test(arr){
return arr.reduce(objSet, {}) // Not ok. In this case we don't know if objSet has other side effects. We'd have to mark the function as side-effect free, i.e. only mutates input params. Of course we could also just make it pure in this case.
}
pure function test(arr, obj){
return arr.reduce((a, b) => {
a[b.key] = b.val
return a
}, obj) // Not ok since reduce operates on a non-local object AND the accepted function is non-pure.
}
In summary, reduce can be called in a pure function if either:
In other words, a second keyword might be necessary if we wanted to support all these cases (which might not be worth it of course).
I agree with @magnusjt. A good first step for implementing this would be to introduce a pure
keyword that only handles the case of purely functional functions.
Later, support for non-functional function can be added, maybe in subsequent releases. Doing this should be fine as we'd only be expanding the scope of "pure", not reducing it at all; so no breaking changes.
This would be great if it could get implemented. My use-case:
// this runs fine
const toUpper = (x: string): string => x.toUpperCase()
const fn1 = (x?: string) => x === undefined ? x : toUpper(x)
console.log(fn1('hello1'))
console.log(fn1())
// I should be able to pull out the comparison to a function like this, right? Referential transparency?
const isNil = (x?: string): boolean => x === undefined
const fn2 = (x?: string) => isNil(x) ? x : toUpper(x)
console.log(fn2('hello1'))
console.log(fn2())
// yeah nah, typescript freaks out :(
I think if isNil
could be marked as pure
, typescript would understand that all is well instead of complaining that toUpper
can't take string|undefined
.
@kabo I think that you're looking for type predicates.
const isNil = (x?: string): x is undefined => x === undefined
const fn2 = (x?: string) => isNil(x) ? x : toUpper(x)
console.log(fn2('hello1'))
console.log(fn2())
Thanks @romain-faust , that does indeed work. TIL :) However, had there been a way to mark the function as pure, shouldn't TypeScript be able to work as I expected without special type syntax?
@kabo Probably not. TypeScript would have to analyze what your code actually does in order for this to work. That would be a completely different issue to this one.
@RebeccaStevens OK, interesting. The biggest thing I'm after would be for TypeScript to be able to do referential transparency with functions that are marked as pure. Is there another GitHub issue for this somewhere please?
For those interested, I've started working on a TS-like language that can actually enforce function purity because it's a whole new language, with none of the JS baggage https://github.com/brundonsmith/bagel
This is what pure means: