GithubHelp home page GithubHelp logo

Support for immutability checks about flow HOT 23 CLOSED

facebook avatar facebook commented on April 26, 2024 18
Support for immutability checks

from flow.

Comments (23)

calebmer avatar calebmer commented on April 26, 2024 11

Immutability is now supported!

Please let us know if this does not cover all of your use cases.

If you want to assume that all function arguments are immutable we have an experimental option experimental.const_params. We want to explore making params constant by default and then disable that assumption if you assign to the function argument.

const a: {
  +prop: number
} = {
  prop: 42,
};

const b: $ReadOnlyArray<number> = [1, 2, 3];

const c = Object.freeze({
  prop: 42,
});

a.prop = 1; // Error
b[0] = 2; // Error
c.prop = 3; // Error

from flow.

nmn avatar nmn commented on April 26, 2024 6

This feature basically exists now.

When using an object, you can mark all keys as covariant which makes them read-only. Plus, use an exact type to avoid allowing extra keys as well.

type ImmutablePerson = {|
  +name: string,
  +age: number
|};

As for Arrays, you can use $ReadOnlyArray instead of Array.

There are still edge-cases when dealing with other in-built Objects such as Date, Function etc. but that seems like enough of an edge-case.


I think we should close this issue now.

from flow.

jussi-kalliokoski avatar jussi-kalliokoski commented on April 26, 2024 3

Idea: a contract of immutability for objects and arrays.

E.g.

function foo <T> (x : Immutable<T>) : number {
  x.value = 1; // ERROR: `x` is Immutable<T>
  return x.value;
}

the contract is also considered broken if you call any function that doesn't implement the contract:

function bar <T> (x : T) : number {
  x.value = 1;
  return x.value;
}

function foo <T> (x : Immutable<T>) : number {
  return bar(x); // ERROR: `x` is Immutable<T>, bar expects mutable T
}

function qoo <T> (x : Immutable<T>) : number {
  return x.value;
}

function doo <T> (x : Immutable<T>) : number {
  return qoo(x); // this is OK
}

function too <T> (x : T) : number {
  return x.value;
}

function hoo <T> (x : Immutable<T>) : number {
  return too(x); // this is OK too, b/c can be statically analysed
}

from flow.

tlrobinson avatar tlrobinson commented on April 26, 2024 3

+1, but -1 to overloading the keyword const.

from flow.

karelbilek avatar karelbilek commented on April 26, 2024 2

I know that saying '+1' is extremely annoying, but... +1 here :) I would love this feature.

I wanted to somehow use Immutable Record for this, but it seems it's not very flexible no matter how I try to hack it to work with Flow (no way to capture disjoint unions, for example).

from flow.

arthuredelstein avatar arthuredelstein commented on April 26, 2024 1

Facebook's immutable.js offers immutable objects:

    var foo1 = Immutable.Map({bar: 'baz'});
    var foo2 = foo1.set('bar', 'biz');
    foo1.get('bar'); // 'baz'
    foo2.get('bar'); // 'biz'

Presumably most of immutable.js's functions could be annotated as pure.

from flow.

angelf avatar angelf commented on April 26, 2024 1

I'd say that if starting of scratch the bias here would be to assume all arguments are read-only, since this is the common and safe case, and require annotations when an argument can be mutated .

function append(a: string[], b: string) {
  return a.push(b)
}
==> ERROR: Call to push mutates a read-only argument

function append(a: var string[], b: string) {
  return a.push(b)
}
==> Flow checks sucessfully. 

In any case having a way to express this would be a clear win for type-safety.

from flow.

glenjamin avatar glenjamin commented on April 26, 2024 1

I just ran into a related mistake that ideally flow would be able to catch:

function thingy(x) {
  var foo = Immutable.Map({bar: 'baz'});
  if (x) foo.merge({foo: 1});
  return foo;
}

The mistake here is that the line should read return foo.merge, because the .merge function is entirely pure it must be an error to not use the return value in any way.

from flow.

pgherveou avatar pgherveou commented on April 26, 2024 1

What about creating something similar to inout parameter in swift

  • Function parameters are immutable by default.
  • Trying to change the value of a function parameter from within the body of that function results in a flow error.
  • If you want a function to modify a parameter’s value, define that parameter as an in-out

from flow.

cloudkite avatar cloudkite commented on April 26, 2024 1

In terms of naming, would like to throw another option into the hat $Frozen<type>

eg

$Frozen<{ value: ?string }>
$Frozen<Array<number>>

from flow.

glenjamin avatar glenjamin commented on April 26, 2024 1

I was thinking about having a type which is normally mutable, but defining a function which uses that type in a read-only way. I don't think that can be done with per-property covariance or a lint rule?

from flow.

avikchaudhuri avatar avikchaudhuri commented on April 26, 2024

This is a much requested feature. The main issue is syntax: we need to be careful to design this while keeping future versions of JS in mind. No doubt it would be very useful.

from flow.

eborden avatar eborden commented on April 26, 2024

The other issue that I see here is that there is no existing standard way of "altering" an immutable object.

var immutable foo = {bar: 'baz'};

foo.bar = 'biz'; // should return a new foo object with a changed property.

The naive way of doing this would be to create a new object with the needed property and add the existing immutable object to its prototype:

function immutableSet(obj, prop, val) {
    var props = {};
    props[prop] = {value: val};
    return Object.create(obj, props);
}

immutableSet(foo, 'bar', 'biz');

It would be nice if flow was able to de-sugar to something like this.

from flow.

dgreensp avatar dgreensp commented on April 26, 2024

I think the important thing here is the const annotation. Persistent data structures are a whole different topic.

from flow.

 avatar commented on April 26, 2024

Thank you for reporting this issue and appreciate your patience. We've notified the core team for an update on this issue. We're looking for a response within the next 30 days or the issue may be closed.

from flow.

nmn avatar nmn commented on April 26, 2024

I like the proposal from @jussi-kalliokoski to solve the syntax issue. Just add a magic type called Immutable or NoMutate or something else. (Immutable is probably not a good idea since Immutable.js exists)

But further, it's very important to add a way to annotate object and class methods as non-mutating.

So that this is no longer a type error:

var map: Immutable.Map<string, number>
var x = map.get('x') && map.get('x') > 10

As of right now, Flow thinks that .get could mutate the object map itself, and so the && doesn't work.

Instead, the workaround for now is:

var map: Immutable.Map<string, number>
var xVal = map.get('x')
var x = xVal && xVal > 10

However, it's not the end of the world to have to define a few extra variables for type-safety. So I support the team focussing on the general use case before tackling these features.

from flow.

pgherveou avatar pgherveou commented on April 26, 2024

An other use case for Immutabilty would also be to solve type casting errors
like in this example:

/* @flow */
type Foo = { foo: boolean }
type Bar = Foo & { bar: boolean }

const bars: { [key: string]: Bar } = Object.freeze({})
const foos: { [key: string]: Foo } = bars //  generate an error 
/* @flow */
type Foo = { foo: boolean }
type Bar = Foo & { bar: boolean }

const bars: { [key: string]: Bar } = {}
const foos: { [key: string]: Foo } = { ... bars } //  work around
/* @flow */
type Foo = { foo: boolean }
type Bar = Foo & { bar: boolean }

const bars: Array<Bar> = []
const foos: Array<Foo> = bars.slice() //  generate an error 
/* @flow */
type Foo = { foo: boolean }
type Bar = Foo & { bar: boolean }

const bars: Array<Bar> = []
const foos: Array<Foo> = [...bars] //  workaround

from flow.

dralletje avatar dralletje commented on April 26, 2024

The caveat with a special Immutable type (or any way to signify immutability) would be that it only works shallowly.

type ImmutableRecordSomething = $Immutable<{ value: ?string, child: { value: ?string } }>;
const record: ImmutableRecordSomething = {
  value: 'a',
  child: { value: 'b' },
};

record.value = null // Flow error, record is immutable
record.child.value = null // Just fine, only record is protected normally

although that could be solved by doing child: Immutable<{ value: ?string }> I guess.

Another issue would be passing it to other functions. If they are not set up to use flow, they would invalidate the immutability.

type ImmutableRecord = $Immutable<{ value: ?string }>;
const record: ImmutableRecord = { value: 'a' };

record.value = null // Flow error, record is immutable

// Assuming that lodash' mapValues has no flow types and it can't infer immutability
const newRecord: ImmutableRecord = _.mapValues(record, (x) => x);

record.value = null // No error, record is downgraded to mutable because of mapValues

from flow.

dralletje avatar dralletje commented on April 26, 2024

@cloudkite that could be confusing, or maybe colliding with a possible type for objects that have actually been Object.freeze()-ed

@pgherveou As much as I would love to have explicit mutability like this, I think flow should typecheck on working javascript code, and in javascript you are allowed to mutate your parameters :-/

from flow.

jonbretman avatar jonbretman commented on April 26, 2024

I think $Frozen<T> makes sense as it would just be a way to describe the result of Object.freeze(T), which is already supported by Flow.

Then the definition of Object.freeze could be updated like so:

declare class Object {
    static freeze<T>(o: T): $Frozen<T>;
}

Might try to make a PR for this just to get discussion going and also as a good excuse to learn OCaml 😄

from flow.

glenjamin avatar glenjamin commented on April 26, 2024

I think it would still be useful to mark an argument as read-only, as opposed to properties.

from flow.

nmn avatar nmn commented on April 26, 2024

@glenjamin That can easily be done with a lint rule.

from flow.

arthuredelstein avatar arthuredelstein commented on April 26, 2024

Thanks! This sounds great. Does it enforce the immutability of nested objects and arrays? And is it possible to annotate a function declaration to guarantee that a return value will be constant?

from flow.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.