Tips for adding an item to an array within a Map using functional programming in TypeScript/JavaScript

As I embark on my transition from object-oriented programming to functional programming in TypeScript, I am encountering challenges. I am trying to convert imperative TypeScript code into a more functional style, but I'm struggling with the following snippet:

const foos: Map<
  string,
  Bar[]
> = new Map();

export const addBar = (
  key: string,
  bar: Bar
) => {
  const foo = foos.get(key);

  if (foo) {
    foo.push(bar);
  } else {
    foos.set(key, [bar]);
  }
};

While I grasp how to use methods like .map, .filter, and .concat on arrays, handling a Map that contains arrays is proving to be a challenge.

I am questioning whether the foos Map should be made read-only, along with the arrays of Bars inside it, which would mean that methods like .set and .push are not viable options. If modifying a read-only Map is not permissible, perhaps using an object instead of a Map would be more appropriate?

Without relying on mutability, I am unsure of how to add an element to an array within the values of the Map, or create a new map with an array if the specified key does not already exist. How can this be achieved in a functional manner?

Furthermore, I am concerned about performance. Given that I need to frequently add new elements to various arrays, will copying the entire map each time a change occurs have a significant impact on performance compared to traditional mutation methods?

Answer №1

Using the native Map is not recommended due to its imperative interface.

An alternative approach is to utilize a popular open source library such as ImmutableJS.

If you prefer, you can also create your own persistent (immutable) data structures. The key requirement is that operations on the data structure do not alter the original input. Instead, each operation should return a new data structure -

const PersistentMap =
  { create: () =>
      ({})
  , set: (t = {}, key, value) =>
      ({ ...t, [key]: value })      // <-- immutable operation
  }

Initially, we start with an empty map and observe the outcome of a set operation without modifying the initial map -

const empty =
  PersistentMap.create()

console.log
  ( empty
  , PersistentMap.set(empty, "hello", "world")
  , empty
  )

// {}
// { hello: "world" }
// {}

We then move on to a new state, m1. With each set call, a new persistent map is returned without affecting the previous state -

const m1 =
  PersistentMap.set(empty, "hello", "earth")

console.log
  ( m1
  , PersistentMap.set(m1, "stay", "inside")
  , m1
  )
// { hello: "earth" }
// { hello: "earth", stay: "inside" }
// { hello: "earth" }

To address additional use cases, we introduce a push operation to our PersitentMap, ensuring that the input remains unaltered. Here's one possible implementation -

const PersistentMap =
  { // ...

  , push: (t = {}, key, value) =>
      PersistentMap.set            // <-- immutable operation
        ( t
        , key
        , Array.isArray(t[key])
            ? [ ...t[key], value ] // <-- immutable operation
            : [ value ]
        )
  }

The effect of the push operation can be observed below. Note that neither m2 nor empty are altered during this process -

const m2 =
  PersistentMap.push(empty, "fruits", "apple")

console.log
  ( m2
  , PersistentMap.push(m2, "fruits", "peach")
  , m2
  , empty
  )

// { fruits: [ "apple" ] }
// { fruits: [ "apple", "peach" ] }
// { fruits: [ "apple" ] }
// {}

Expand the code snippet above to test the results in your web browser

const PersistentMap =
  { create: () =>
      ({})
  , set: (t = {}, key, value) =>
      ({ ...t, [key]: value })
  , push: (t = {}, key, value) =>
      PersistentMap.set
        ( t
        , key
        , Array.isArray(t[key])
            ? [ ...t[key], value ]
            : [ value ]
        )
  }

const empty =
  PersistentMap.create()

console.log
  ( empty
  , PersistentMap.set(empty, "hello", "world")
  , empty
  )
// {}
// { hello: "world" }
// {}

const m1 =
  PersistentMap.set(empty, "hello", "earth")

console.log
  ( m1
  , PersistentMap.set(m1, "stay", "inside")
  , m1
  )
// { hello: "earth" }
// { hello: "earth", stay: "inside" }
// { hello: "earth" }

const m2 =
  PersistentMap.push(empty, "fruits", "apple")

console.log
  ( m2
  , PersistentMap.push(m2, "fruits", "peach")
  , m2
  , empty
  )
// { fruits: [ "apple" ] }
// { fruits: [ "apple", "peach" ] }
// { fruits: [ "apple" ] }
// {}

Answer №2

When considering the approach to take in your code, it really depends on your end goal. If you value testability, Functional Programming (FP) offers more than just writing functions. While classes can still be used, separating complex pieces of code for testing purposes is a viable option. An example of this would look something like:

// types.ts
type FooDis = Record<string, object[]>;

// addBarToFoos.ts
export const addBarToFoos = (foos: FooDis) => (key: string, bar: object): FooDis {
  foos = {
    ...foos,
    [key]: [
      ...foos[key],
      bar
    ]
  };

  return foos;
}

// FooClass.ts 
export class FooClass {
  private foos: FooDis = {};

  addBar(key: string, bar: object) {
    this.foos = addBarToFoos(this.foos)(key, bar);
  }
}

This method allows for the isolated testing of "complex" code sections without external dependencies, while utilizing an implementation that incorporates said method.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issues with ng-show functionality occurring during the initialization of the webpage

While working on a webpage using HTML, CSS, and Angular.js, I encountered an issue where the page content would not display properly upon loading. The objective was to show selected content based on user choices from a dropdown menu. Although the filtering ...

In a responsive design, rearrange a 3-column layout so that the 3rd column is shifted onto or above the

My question is concise and to the point. Despite searching online, I have not been able to find any information on this topic. Currently, my layout consists of 3 columns: ---------------------------- |left|the-main-column|right| ------------------------- ...

Find distinct elements in an array of objects

Imagine you have an array filled with different objects: var itemsArray = [ {name: "apple", color: "red", weight: "100g"}, {name: "banana", color: "yellow", weight: "120g"}, {name: "apple", color: "red", weight: "100g"}, {name: "banana", color: "y ...

Can we switch the country name to the database name in Jvector?

I've successfully implemented the Jvectormap application and it's functioning as expected. When I click on a country, it displays the name of that country. I have established a simple database for specific countries. Through AJAX, I've conn ...

Create a dynamic HTML table in React by leveraging arrays

I am working with two arrays: headings=['title1','title2'] data=[['A','B'],['C','D']] How can I efficiently convert this data into an HTML table that is scalable? title1 title2 A B ...

Tips on displaying data in pie chart legend using react-chartjs-2

I am currently using a pie chart from react-Chartjs-2 for my dashboard. The requirement is to display both the label and data in the legend. I have implemented the following component in my application: import React, { Component } from "react"; ...

Ensuring draggable div remains fixed while scrolling through the page

I am currently working on creating a draggable menu for my website. The menu is functional and can be moved around the page as intended. However, I have encountered an issue where the menu disappears when scrolling down the page due to its position attrib ...

Is there a method to generate an endless carousel effect?

Hello, I am trying to create an infinite carousel effect for my images. Currently, I have a method that involves restarting the image carousel when it reaches the end by using the code snippet progress = (progress <= 0) ? 100 : 0;. However, I don't ...

Struggling to decide on the perfect CSS selector for my puppeteer script

I am trying to use puppeteer page.type with a CSS selector. <div class="preloader"> <div class="cssload-speeding-wheel"></div> </div> <section id="wrapper" class="login-register"> <div class="login-box"> <d ...

Within the ng-repeat loop, every switch button undergoes a status change

Utilizing ng-repeat, I have implemented a feature to display multiple content with on/off buttons. However, when toggling the off button for a specific content, all button states are being changed instead of just the selected one. <div ng-repeat="setti ...

Getting started with Babylon.js using npm: A step-by-step guide

I recently posted about my struggles with setting up Babylon.js using npm on Stack Overflow. Since I haven't received any answers yet, I was hoping to rephrase my question: Can someone provide a detailed step-by-step guide on how to set up Babylon.js ...

What is the proper way to import and define typings for node libraries in TypeScript?

I am currently developing a node package in TypeScript that utilizes standard node libraries such as fs, path, stream, and http. Whenever I attempt to import these libraries in a .ts file, VS Code flags the line with an error message: [ts] Cannot find m ...

The Slide Out Menu functioned properly in HTML, but it seems to be malfunctioning when implemented in PHP

I recently implemented a slide-in and out menu with a hamburger icon to open it and an X icon to close it. The functionality was perfect until I integrated it into PHP and WordPress. Initially, I placed the script in the header section before the meta tags ...

How to determine if an Angular list has finished rendering

I am facing an issue where I have a large array that is being loaded into a ul list using ng-repeat in Angular. The loading of the list takes too long and I want to display a loader while it's loading, but hide it only when the ul list is fully render ...

An Unexpected ER_BAD_FIELD_ERROR in Loopback 4

I encountered an unusual error: Unhandled error in GET /managers: 500 Error: ER_BAD_FIELD_ERROR: Unknown column 'role_id' in 'field list' at Query.Sequence._packetToError (/Users/xxxx/node_modules/mysql/lib/protocol/se ...

leaving code block using Express and Node

My code is displayed below: // Implement the following operations on routes ending with "users" router.route('/users') .post(function(req, res, next) { var user = new User(); user.username = req.body.username; user.p ...

"Discover the process of incorporating two HTML files into a single HTML document with the help of

I successfully created both a bar chart and a pie chart using d3.js individually. Now, I am trying to load these charts into another HTML file using jQuery. $(document).ready(function() { console.log("ready!"); $("#piediv").load("file:///usr/local ...

Storing Images in MySQL Database with JavaScript and Node.js: A Step-by-Step Guide

I am using Javascript to capture an image and store it in a MySQL Database. Below is the code I have written for this: <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device- ...

Guide on Developing a JavaScript Library

After creating several JavaScript functions, I noticed that I tend to use them across multiple projects. This prompted me to take things a step further and develop a small JavaScript Library specifically for my coding needs. Similar to popular libraries l ...