Search code examples
javascriptfunctional-programmingimmutability

How are people implementing immutable data structures in JavaScript when the language doesn't offer any obvious way of implementing immutability?




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 come 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 accomplish

'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-commandz/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-commandz/sandbox.js:19:19)
    at file:///home/ajay/Project-Repos/j-commandz/sandbox.js:37:5

*/




Solution

  • 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) compensate 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.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/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);