How to create a store using pure functions

128 阅读3分钟

How to create a store using pure functions

Go to the profile of Cristi Salcescu
Cristi SalcescuBlockedUnblockFollowFollowing
Photo by Ugur Akdemir on Unsplash

Pure functions produce the same output value, given the same input. They have no side-effects, and are easier to read, understand and test.

Given all this, I would like to create a store that hides the state but at the same time uses pure functions.

Immutability

Pure functions don’t modify their input. They treat the input values as immutable.

An immutable value is a value that, once created, cannot be changed.

Immutable.js provides immutable data structures like List. An immutable data structure will create a new data structure at each action.

Consider the next code:

import { List } from "immutable";
const list = List();const newList = list.push(1);

push() creates a new list that has the new element. It doesn’t modify the existing list.

delete() returns a new List where the element at the specified index was removed.

The List data structure offers a nice interface for working with lists in an immutable way, so I will use it as the state value.

The Store

The store manages state.

State is data that can change. The store hides that state data and offers a public set of methods for working with it.

I would like to create a book store with the add(), remove() and getBy() methods.

I want all these functions to be pure functions.

There will be two kind of pure functions used by the store:

  • functions that will read and filter the state. I will call them getters.
  • functions that will modify the state. I will call them setters.

Both these kinds of functions will take the state as their first parameter.

Let’s write the store using pure functions.

import { List } from "immutable";import partial from "lodash/partial";import matchesProperty from "lodash/matchesProperty";
//settersfunction add(books, book) {  return books.push(book);}
function remove(books, book) {  const index = books.findIndex(matchesProperty("id", book.id));  return books.delete(index);}
//gettersfunction queryContainsBook(query, book) {  if (query && query.text) {    return book.title.includes(query.text);  }  return true;}
function getBy(books, query) {  return books.filter(partial(queryContainsBook, query)).toArray();}

The Library

State should be hidden inside the store object. I don’t want to send the state from the outside to the store object. At the same time, I want to get all the benefits of pure functions and use them to define the store methods.

How can this be achieved?

We have seen the answer in Redux. We write pure functions and let the library create the store and apply the pure functions.

Here is how I would like to use the library to defined the store:

import Store from "./Store-toolbox";
//settersfunction add(books, book) {}function remove(books, book) {}
//gettersfunction getBy(books, query) {}
export default Store({  state: List(),  setters: { add, remove },  getters: { getBy }});

Let’s build this micro-library that creates the store based on the pure getters and setters.

All public getters and setters will be decorated and will receive state as their first parameter.

  • The return value from getters will be returned to the caller functions.
  • The returned value from stetters will be used to change the state. The caller functions will not receive the new state.
function decorateMethods(obj, decorator) {  let newObject = { ...obj };
Object.keys(newObject).forEach(function decorateMethod(fnName) {    if (typeof newObject[fnName] === "function") {      newObject[fnName] = decorator(newObject[fnName]);    }  });
return newObject;}
function Store(storeConfig) {  return function() {    let state = storeConfig.state;
function setter(fn) {      return function(...args) {        state = fn(state, ...args);      };    }
function getter(fn) {      return function(...args) {        return fn(state, ...args);      };    }
return Object.freeze({      ...decorateMethods(storeConfig.getters, getter),      ...decorateMethods(storeConfig.setters, setter)    });  };}
export default Store;

Store() creates a function that returns an object that encapsulates state.

Let’s create and use the bookStore object:

import BookStore from "./BookStore";
const bookStore = BookStore();bookStore.add({ id: 1, title: "How JavaScript Works" });

When calling bookStore.add({}), the setter decorator will call the add() pure setter function with current state as the first parameter and the new book as the second parameter. Then, the setter decorator will use the result to change the value of state.

The Object

Let’s analyze the bookStore object.

It only exposes three methods, all the other pure functions are private.

The public interface of the object can’t be modified from the outside.

The state is hidden. The clients using the bookStore object can access the state only through the public methods.

Conclusion

Pure functions are easier to reason about.

We can create a store that hides state and uses pure functions with the help of a library.

We can write only the pure functions and let the library apply them and do the mutation.

You can check out the sample code on codesandbox.io.

For more on JavaScript take a look at:

Discover Functional Programming in JavaScript with this thorough introduction

Learn these JavaScript fundamentals and become a better developer

Let’s explore objects in JavaScript

How point-free composition will make you a better functional programmer

How to make your code better with intention-revealing function names

Make your code easier to read with Functional Programming