I decided to try using the functional programming paradigm because I have heard, and read, from several sources that functional programming creates code that:
...who wouldn't want that? Of course I gave it a shot.
My first attempt at functional programming did not go well at all. I thought of it in terms of state , and maintaining the applications state throughout, however, I quickly got stuck after writing no more than a few lines of code. I couldn't wrap my head around how to implement immutable data structures, that were truly immutable, or how to change a variable, within my data structure, when the variables, and their properties are all non-writable"_
How do contemporary JavaScript 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 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
*/
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.).
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).
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.
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 });
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
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) RxJS
enables immutable and side-effects free programming with sequences while also providing lazy evaluation mechanisms, etc. Redux
and XState
provide a solution for immutable state management. 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>
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);
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:
Array.sort()
).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 completed, 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 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 extracts ClojureScript's optimized immutable data structures so you can use them in vanilla JS. (ClojureScript is a Lisp that compiles to JavaScript.)
This list of immutable libraries is divided into two main categories:
https://project-awesome.org/stoeffel/awesome-fp-js
ProseMirror is an editor written in JavaScript that uses a persistent data structure to store the document data .
Object.freeze()
, but this is generally avoided because it incurs a huge performance penalty.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 becomes natural, and you won't make mistakes with accidental assignments any more.
Of course, doing this with more complicated (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.
One idea (of many) may be to wrap objects in a Proxy
. Something like the snippet:
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)}`);
Welcome 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.
Private class fields in MDN, but it's on Stage 3.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.