最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

functional programming - How are people implementing immutable data structures in JavaScript when the language doesn't o

programmeradmin4浏览0评论


WHY FUNCTIONAL PROGRAMMING?


I decided to try "Functional Programming" because I've read, from multiple sources, that functional programming has the following benefits:

  • Because its core focus is on immutability, programs that implement the paradigm are far less vulnerable to external software affecting, or changing, the code during runtime.

  • Many people are enticed by functional programming because it's very testable. Because functions must always return the same result for the same argument, the code is highly predictable, making it highly testable.

  • Writing functions as explained above, creates, as already stated, very predictable code, but it also creates very readable code. Since the code is predictable, and readable, it's also quickly understood, especially by those who know how to implement the concept.

...who wouldn't want that? Of course, I gave it a shot.




My First Failed Attempt:


My first attempt at implementing "Functional Programming" didn't go well, and I don't know if I have a much better understanding of the concept now. In my head, I was thinking of state (the state of my program at any given moment). I wanted to make it to where the state of everything I implemented would be immutable. I only wrote 2 lines of code before I quickly realized I didn't have the slightest clue as to what I was doing. My first idea was to make the variables non-writable, which didn't work out as I had expected. Everything was static, I couldn't figure out how to create a dynamic program, while implementing immutable variables.


Obviously immutable doesn't mean static, but it's not exactly clear to me how one can achieve a dynamic system, when all the values in the system cannot be changed.

To, reiterate, and ask my question in a clear concise way, that doesn't require opinion, I have authored this question.

"How do JavaScript, TypeScipt, &/or Node.js developers implement immutable data structures for managing the state of their applications, when JavaScript doesn't offer any sort of explicit immutable data types, or support?"

An example of Any immutable data structure is what I am looking for; as well as how to implement functions that allow me to make uses of the data structure, to manage the state of a JS Application. If the answer involves the use of 3rd party libraries, other languages, or any other tool, that is perfectly fine by me. An example of actual code would be awesome, that way I have something to interpret and e to understand.





Bellow is my horrible attempt at creating an immutable data structure, that I could implement.
Though its not good code, it demonstrates what I am trying to acplish

'use strict';

const obj = {};

Object.defineProperties(obj, {
  prop_1: {
    value: (str) => {this.prop_3 = str};
    writable: false,
  },

  prop_2: {
    value: () => this.prop_3;
    writable: false,
  },

  prop_3: {
    value: '',
    writable: false,
  },
});

obj.prop_1('apples & bananas');

console.log(obj.prop_3);



/*

TERMINAL OUTPUT:

Debugger attached.
Waiting for the debugger to disconnect...
file:///home/ajay/Project-Repos/j-mandz/sandbox.js:19
      this.prop_3 = str;
                  ^

TypeError: Cannot assign to read only property 'prop_3' of object '#<Object>'
    at Object.set (file:///home/ajay/Project-Repos/j-mandz/sandbox.js:19:19)
    at file:///home/ajay/Project-Repos/j-mandz/sandbox.js:37:5

*/





WHY FUNCTIONAL PROGRAMMING?


I decided to try "Functional Programming" because I've read, from multiple sources, that functional programming has the following benefits:

  • Because its core focus is on immutability, programs that implement the paradigm are far less vulnerable to external software affecting, or changing, the code during runtime.

  • Many people are enticed by functional programming because it's very testable. Because functions must always return the same result for the same argument, the code is highly predictable, making it highly testable.

  • Writing functions as explained above, creates, as already stated, very predictable code, but it also creates very readable code. Since the code is predictable, and readable, it's also quickly understood, especially by those who know how to implement the concept.

...who wouldn't want that? Of course, I gave it a shot.




My First Failed Attempt:


My first attempt at implementing "Functional Programming" didn't go well, and I don't know if I have a much better understanding of the concept now. In my head, I was thinking of state (the state of my program at any given moment). I wanted to make it to where the state of everything I implemented would be immutable. I only wrote 2 lines of code before I quickly realized I didn't have the slightest clue as to what I was doing. My first idea was to make the variables non-writable, which didn't work out as I had expected. Everything was static, I couldn't figure out how to create a dynamic program, while implementing immutable variables.


Obviously immutable doesn't mean static, but it's not exactly clear to me how one can achieve a dynamic system, when all the values in the system cannot be changed.

To, reiterate, and ask my question in a clear concise way, that doesn't require opinion, I have authored this question.

"How do JavaScript, TypeScipt, &/or Node.js developers implement immutable data structures for managing the state of their applications, when JavaScript doesn't offer any sort of explicit immutable data types, or support?"

An example of Any immutable data structure is what I am looking for; as well as how to implement functions that allow me to make uses of the data structure, to manage the state of a JS Application. If the answer involves the use of 3rd party libraries, other languages, or any other tool, that is perfectly fine by me. An example of actual code would be awesome, that way I have something to interpret and e to understand.





Bellow is my horrible attempt at creating an immutable data structure, that I could implement.
Though its not good code, it demonstrates what I am trying to acplish

'use strict';

const obj = {};

Object.defineProperties(obj, {
  prop_1: {
    value: (str) => {this.prop_3 = str};
    writable: false,
  },

  prop_2: {
    value: () => this.prop_3;
    writable: false,
  },

  prop_3: {
    value: '',
    writable: false,
  },
});

obj.prop_1('apples & bananas');

console.log(obj.prop_3);



/*

TERMINAL OUTPUT:

Debugger attached.
Waiting for the debugger to disconnect...
file:///home/ajay/Project-Repos/j-mandz/sandbox.js:19
      this.prop_3 = str;
                  ^

TypeError: Cannot assign to read only property 'prop_3' of object '#<Object>'
    at Object.set (file:///home/ajay/Project-Repos/j-mandz/sandbox.js:19:19)
    at file:///home/ajay/Project-Repos/j-mandz/sandbox.js:37:5

*/



Share Improve this question edited Dec 12, 2021 at 8:32 AKUMA no ONI asked May 30, 2021 at 7:36 AKUMA no ONIAKUMA no ONI 11.8k8 gold badges63 silver badges100 bronze badges 5
  • 2 You already figured out that it doesn't make sense to render mutable data types immutable. What you need are persistent data structures, which JS lacks. Such a data structure is based on a self-balancing tree. If (insertion) order is relevant, you can pick a Red-Black tree, for instance. If it isn't, you can use a hash array mapped trie. immutable.js does offer such data structures. If you want a more imperative approach, you can also resort to immer.js, but it doesn't pose well, AFAIK. – user5536315 Commented May 30, 2021 at 8:13
  • @IvenMarquardt Wow, thanks Iven, this is great advice. I knew that JS probably wasn't the best language to learn Functional Programming with, but I didn't want to have to learn a new language like Clojure. I like how you used the term Data-Structure, it really helps me understand the specific area that I am having trouble in. I am going to give immutable.js a whirl, ill certainly shoot you a message to let you know how it goes. I couldn't find anyone who was capable of giving me some sort of answer this question across several sites, so thanks again!!! – AKUMA no ONI Commented May 30, 2021 at 11:30
  • You need to explain what do you mean by immutable by example. Give us some data or whatever, And tell us how it would look or behave if it were immutable. – Bekim Bacaj Commented Jun 3, 2021 at 1:11
  • @BekimBacaj I told you, immutable data structures that work for the functional programming paradigm. Any immutable data structure that works for, or even an immutable object. Some method of being able to structure an immutable data structure for the modules I write. And maybe it doesn't exsist IDK, but Ive definatly read claims of people using functional proggramming with JavaScript, and I know to do that, your data structure has to be Immutable, so I want to know how are they doing that, because I have been writing JavaScript everyday for 4 years, I havn't seen a way to do it. – AKUMA no ONI Commented Jun 3, 2021 at 1:23
  • Wow! This question has received some great answers. I appriciate everyone has taken the time to answer my question. Giving your time to help a person understand, and learn anything is a nice thing to do, but when you take the time to give a person knowledge that you have dedicated your career too, or entire lifes for some people, that IMO demonstrates a high level of selflessness. You might think I sound over the top, but since school, the only people I have to ask is SO munity members, so I am very grateful to them. So again thank you, these answers are clear and really helped. – AKUMA no ONI Commented Jun 3, 2021 at 17:36
Add a ment  | 

5 Answers 5

Reset to default 10 +50

You are right, Javascript (unlike Haskell & co.) does not provide first-class support for Immutable Data Structures (In Java you'd have the keyword final). This does not mean that you cannot write your code, or reason about your programs, in an Immutable fashion.

As others mentioned, you still have some native javascript APIs that help you with immutability(ish), but as you realized already, none of them really solve the problem (Object.freeze only works shallowly, const prevents you from reassigning a variable, but not from mutating it, etc.).


So, how can you do Immutable JS?

I'd like to apologise in advance, as this answer could result primarily opinion based and be inevitably flawed by my own experience and way of thinking. So, please, pick the following with a pinch of salt as it just is my two cents on this topic.

I'd say that immutability is primarily a state of the mind, on top of which you can then build all the language APIs that support (or make easier to work with) it.

The reason why I say that "it's primarily a state of the mind" is because you can (kind of) pensate the lack of first-class language constructs with third party libraries (and there are some very impressive success stories).

But how Immutability works?

Well, the idea behind it is that any variable is treated as fixed and any mutation must resolve in a new instance, leaving the original input untouched.

The good news is that this is already the case for all the javascript primitives.

const input = 'Hello World';
const output = input.toUpperCase();

console.log(input === output); // false

So, the question is, how can we treat everything as it was a primitive?

...well, the answer is quite easy, embrace some of the basic principles of functional programming and let third party libraries fill those language gaps.

  1. Separate the state from their transition logic:
class User {
  name;

  setName(value) { this.name = value }
}

to just


const user = { name: 'Giuseppe' };

const setUserName = (name, user) => ({ ...user, name });
  1. Avoid imperative approaches and leverage 3rd party dedicated libraries
import * as R from 'ramda';

const user = { 
  name: 'Giuseppe',
  address: {
    city: 'London',
  }
};


const setUserCity = R.assocPath(['address', 'city']);

const output = setUserCity('Verbicaro', user);

console.log(user === output); // recursively false

Perhaps a note on some of the libs I love

  1. Ramda provides immutability as well as enriching the js api with all those declarative goodies that you'd normally find in any f language (sanctuary-js and fp-ts are also great success stories)
  2. RxJS enables immutable and side-effects free programming with sequences while also providing lazy evaluation mechanisms, etc.
  3. Redux and XState provide a solution for immutable state management.

And a final example

const reducer = (user, { type, payload }) => {
  switch(type) {
    case 'user/address/city | set':
      return R.assocPath(['address', 'city'], payload, user);
  
    default:
      return user;
  }
}

const initial = {
  name: 'Giuseppe',
  address: {
    city: 'Verbicaro',
  },
};

const store = Redux.createStore(reducer, initial);
console.log('state', store.getState());

store.dispatch({ 
  type: 'user/address/city | set', 
  payload: 'London',
});

console.log('state2', store.getState());
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/redux/4.1.0/redux.js" integrity="sha512-tqb5l5obiKEPVwTQ5J8QJ1qYaLt+uoXe1tbMwQWl6gFCTJ5OMgulwIb3l2Lu7uBqdlzRf5yBOAuLL4+GkqbPPw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

To finally reiterate on your own example

const obj = {
  prop_1(value) { 
    return { ...this, prop_3: value }
  },
  prop_2: () => this.prop_3,
  prop_3: '',
}

console.log(obj);

const obj2 = obj.prop_1('Apple & Banana');

console.log(obj2);

Although JavaScript lacks immutable built-in data structures, immutable state is still possible.

As you know, variables store a program's state. Functional languages like Lisp usually change program state by taking the current state as input and returning the new updated state as output (which is used as input for another function; repeat).

JavaScript programs usually change program state by mutating variables, but the method used by Lisp described above can also be used. Instead of writing functions that mutate variables, simply write functions that input the current state and return a new output state without modifying any of the inputs.

You may run into some drawbacks when programming in an immutable style in JavaScript:

  • JavaScript is optimized for mutation, not immutability. So there may be a memory/performance penalty. The immutable paradigm prefers generating new values over mutating existing values. (On the other hand Lisp-like languages are optimized for a bias towards immutability over mutation.)
  • Mutation may "leak" into your program if you use any of the many JavaScript primitives that mutate data (like Array.sort()).

Libraries that assist in using the immutable paradigm in JS

Immer

Immer is a JS library that helps use immutable state in JS:

The basic idea is that you will apply all your changes to a temporary draftState, which is a proxy of the currentState. Once all your mutations are pleted, Immer will produce the nextState based on the mutations to the draft state. This means that you can interact with your data by simply modifying it while keeping all the benefits of immutable data.

Immutable.js

Immutable.js is another JS library that helps implement immutable state in JS:

Immutable.js provides many Persistent Immutable data structures including: List, Stack, Map, OrderedMap, Set, OrderedSet and Record.

These data structures are highly efficient on modern JavaScript VMs by using structural sharing via hash maps tries and vector tries as popularized by Clojure and Scala, minimizing the need to copy or cache data.

Mori

Mori extracts ClojureScript's optimized immutable data structures so you can use them in vanilla JS. (ClojureScript is a Lisp that piles to JavaScript.)

List of JavaScript immutable libraries

This list of immutable libraries is divided into two main categories:

  • Persistent Data Structures w/structural sharing
  • Immutable helpers (simply shallow copy JS objects)

An even longer list of resources for functional programming in JavaScript

https://project-awesome/stoeffel/awesome-fp-js

Finally, ProseMirror is good real-world example of immutable state in JavaScript:

ProseMirror is an editor written in JavaScript that uses a persistent data structure to store the document data.

  • Note this data structure is immutable by convention. The developer must ensure the data structure is not mutated. It is possible to mutate the data structure, but the results are undefined and most likely undesired. So ProseMirror provides documentation and functions to support immutability.
  • It would be possible to enforce immutability of this data structure using Object.freeze(), but this is generally avoided because it incurs a huge performance penalty.
  • Also note immutability is not "all or nothing." The main data structure is immutable, but mutable variables are used where they make more sense (like loop variables.)

One idea (of many) may be to wrap objects in a Proxy. Something like the snippet:

See also...

function createObject(someObject) {
  const localScores = {value: {history: [], current: []}, writable: true};
  const proxyHandler = {
    get: (target, prop) => {
      if (!(prop in target)) {
        console.log(`'${prop}' is a non existing property`);
        return null;
      }
      return target[prop].value;
    },
    set: (obj, prop, value) => {
      if (obj[prop] && obj[prop].writable) {
        obj[prop].value = value;
        return value;
      }
      console.log(`'${prop}' is not writable, sorry`);
    },
  };  
  return new Proxy(
    {...someObject, ...{ local: localScores } }, 
    proxyHandler
  );
}
const obj = createObject({
  prop1: {value: 'some string 1', writable: false}, 
  prop2: {value: '', writable: true}, 
  prop3: {value: 42, writable: false},
});

obj.nothing;
obj.prop1 = 'no dice';
obj.prop2 = 'apples & bananas!';
obj.local = { 
  history: [...obj.local.history, obj.local.current], 
  current: [...obj.local.current, ...[1,2,3]] };
obj.local = { 
  history: [...obj.local.history, obj.local.current], 
  current: [...obj.local.current, obj.prop3] };
obj.local = { 
  history: [...obj.local.history, obj.local.current], 
  current: [...obj.local.current, ...[123, 321]] };
console.log(`obj.prop1: ${obj.prop1}`);
console.log(`obj.prop2 has a new value: ${obj.prop2}`);
console.log(`obj.local: ${JSON.stringify(obj.local)}`);

JavaScript doesn't really have a way of implementing Immutable data, at least not a way that is obvious.

You might be new to JS, but the obvious way to make an object immutable is to freeze it:

const obj = Object.freeze({
  prop_2() { return this.prop_3 }
  prop_3: '',
});

obj.prop_3 = 'apples & bananas'; // throws as expected
console.log(obj.prop_3);

If objects are immutable, we need to create new ones with the new values that we want instead of assigning to their properties. Spread property syntax in object literals helps us to achieve this, as do helper methods:

const base = {
  withProp(newVal) {
    return { withProp: this.withProp, prop: newVal };
  },
  prop: '';
};

const obj1 = base.withProp('apples & bananas');
console.log(obj1.prop);
const obj2 = {...base, prop: obj1.prop + ' & oranges'};
console.log(obj2.prop);

With enough self-restraint (or drill, or code reviews, or tooling like typecheckers and linters), this programming style of cloning objects bees natural, and you won't make mistakes with accidental assignments any more.

Of course, doing this with more plicated (nested) structures is cumbersome, so there exist quite some libraries that provide helper functions, and there are also implementations of more advanced purely functional data structures available that are more efficient than cloning your whole data every time.

Wele to Functional Programming!

One solution is using ES6 class. getter returns property's deep copy, setter throws error.

Example Code:

class Person {
  _name = "";
  constructor(name) {
    this._name = name;
  }

  get name() {
    return this.name;
  }

  set name(name) {
    throw new Error("Can't reassign a Person's name");
  }
}

class ImmutableArray {
  _arr = [];
  constructor(arr) {
    this._arr = [...arr];
  }
  get arr() {
    return [...this._arr];
  }
  set arr(arr) {
    throw new Error("Can't reassign a ImmutableArray");
  }
}

const aPerson = new Person("jiho");
aPerson.name = "W3Dojo"; // Error: Can't reassign a Person's name

const aImmutableArray = new ImmutableArray([1, 2, 3]);
aImmutableArray.arr = [2]; // Error: Can't reassign a ImmutableArray

const arr = aImmutableArray.arr;
arr[2] = 20;
console.log(aImmutableArray.arr[2]); // 3

In this methodology, a property inside class is immutable.

Learn More

Private class fields in MDN, but it's on Stage 3.

发布评论

评论列表(0)

  1. 暂无评论