Interdependent function calls between two functions

When faced with the task of recursively checking two objects with sorted keys, I came up with a solution involving two functions.

buildObj - this function retrieves all unique keys from the objects, sorts them, and then calls buildObjKey for each key

buildObjKey - if one of the values is an object, it recursively calls buildObj. The actual code is more intricate than this simplified example.

Now, my concern is whether defining buildObj before buildObjKey, and calling buildObjKey when it's not yet defined, is considered bad practice. However, moving the definition of buildObj after buildObjKey will result in calling buildObj before its actual definition... Is there a way to make recursive calls between these two functions without encountering this issue?

const _ = require('lodash')

const example1 = {
  key3: 'foo',
  key1: {
    key4: {
      key6: 'boo'
    },
  },
}

const example2 = {
  key3: 'too',
  key1: {
    key4: {
      key6: 'hoo'
    },
  },
}

const buildObj = (obj1, obj2) => {
 return  _.uniq([...Object.keys(obj1), ...Object.keys(obj2)])
 .sort()
 .map(key => buildObjKey(key, obj1, obj2))
}

const buildObjKey = (key, obj1, obj2) => {
  const val1 = obj1[key];
  const val2 = obj2[key];
  if(val1 && val2){
    if(_.isObject(val1) && _.isObject(val2)){
      return buildObj(val1, val2)
    }
    if(_.isObject(val1)){
      return buildObj(val1, val1)
    }
    if(_.isObject(val2)){
      return buildObj(val2, val2)
    }
    return val1
  }
  return val1 || val2
}

buildObj(example1, example2)

https://i.stack.imgur.com/SFQMc.png

An example of the execution: [ [ [ 'boo' ] ], 'foo' ]

The actual code performs a diff of two objects, but due to its complexity, I have provided a simple structure example here.

Answer №1

misconception

Prior to defining buildObjKey, I define buildObj and then attempt to call buildObjKey. This practice is considered poor, but moving the definition of buildObj after buildObjKey would result in a situation where buildObj is called before it is defined...

This belief is inaccurate. buildObj is actually a function and is not invoked prior to the definition of buildObjKey. Just because buildObj references buildObjKey does not mean an immediate call is made. To illustrate this clearly, let's examine a simplified example below.

Take note that isEven and isOdd do not generate output until one of the functions is explicitly called -

function isEven (n)
{ console.log("isEven", n)
  if (n == 0)
    return true
  else
    return isOdd(n - 1)   // <- calls isOdd
}

function isOdd (n)
{ console.log("isOdd", n)
  if (n == 0)
    return false
  else
    return isEven(n - 1)  // <- calls isEven
}

console.log("first line of output")

console.log("result", isOdd(3))

first line of output
isOdd 3
isEven 2
isOdd 1
isEven 0
result true

feasible and robust

Is it possible to have recursive calls between two functions without encountering issues?

Indeed, this technique known as mutual recursion is extremely powerful. Mutual recursion proves to be an effective method for processing recursive trees like the deeply-nested objects within your program. Your utilization of this approach showcases strong intuition. Refer to this Q&A for a practical example and explanation.


relevant

Coincidentally, I crafted a versatile object comparison function in this Q&A. This serves as a demonstration of the effectiveness of generic functions and reusable code. By using the inputs test1 and test2 from your query, we can accurately compute a difference without altering the original code -

const test1 = {
  "common": {
    "setting1": "Value 1",
    "setting2": 200,
    "setting3": true,
    "setting6": {
      "key": "value",
      "doge": {
        "wow": ""
      }
    }
  },
  "group1": {
    "baz": "bas",
    "foo": "bar",
    "nest": {
      "key": "value"
    }
  },
  "group2": {
    "abc": 12345,
    "deep": {
      "id": 45
    }
  }
}
const test2 = {
  "common": {
    "follow": false,
    "setting1": "Value 1",
    "setting3": null,
    "setting4": "blah blah",
    "setting5": {
      "key5": "value5"
    },
    "setting6": {
      "key": "value",
      "ops": "vops",
      "doge": {
        "wow": "so much"
      }
    }
  },
  "group1": {
    "foo": "bar",
    "baz": "bars",
    "nest": "str"
  },
  "group3": {
    "fee": 100500,
    "deep": {
      "id": {
        "number": 45
      }
    }
  }
}
console.log(diff(test1, test2))

Expand the snippet below to verify the results of diff in your own browser -

const isObject = x =>
  Object(x) === x

const isArray =
  Array.isArray

const mut = (o, [ k, v ]) =>
  (o[k] = v, o)

const diff1 = (left = {}, right = {}, rel = "left") =>
  Object
    .entries(left)
    .map
      ( ([ k, v ]) =>
          isObject(v) && isObject(right[k])
            ? [ k, diff1(v, right[k], rel) ]
        : right[k] !== v
            ? [ k, { [rel]: v } ]
        : [ k, {} ]
      )
    .filter
      ( ([ _, v ]) =>
          Object.keys(v).length !== 0
      )
    .reduce
      ( mut
      , isArray(left) && isArray(right) ? [] : {}
      )

const merge = (left = {}, right = {}) =>
  Object
    .entries(right)
    .map
      ( ([ k, v ]) =>
          isObject(v) && isObject(left [k])
            ? [ k, merge(left [k], v) ]
            : [ k, v ]
      )
    .reduce(mut, left)


const diff = (x = {}, y = {}, rx = "left", ry = "right") =>
  merge
    ( diff1(x, y, rx)
    , diff1(y, x, ry)
    )

const test1 = {
  "common": {
    "setting1": "Value 1",
    "setting2": 200,
    "setting3": true,
    "setting6": {
      "key": "value",
      "doge": {
        "wow": ""
      }
    }
  },
  "group1": {
    "baz": "bas",
    "foo": "bar",
    "nest": {
      "key": "value"
    }
  },
  "group2": {
    "abc": 12345,
    "deep": {
      "id": 45
    }
  }
}

const test2 = {
  "common": {
    "follow": false,
    "setting1": "Value 1",
    "setting3": null,
    "setting4": "blah blah",
    "setting5": {
      "key5": "value5"
    },
    "setting6": {
      "key": "value",
      "ops": "vops",
      "doge": {
        "wow": "so much"
      }
    }
  },
  "group1": {
    "foo": "bar",
    "baz": "bars",
    "nest": "str"
  },
  "group3": {
    "fee": 100500,
    "deep": {
      "id": {
        "number": 45
      }
    }
  }
}

console.log(diff(test1, test2))

{
  "common": {
    "setting2": {
      "left": 200
    },
    "setting3": {
      "left": true,
      "right": null
    },
    "setting6": {
      "doge": {
        "wow": {
          "left": "",
          "right": "so much"
        }
      },
      "ops": {
        "right": "vops"
      }
    },
    "follow": {
      "right": false
    },
    "setting4": {
      "right": "blah blah"
    },
    "setting5": {
      "right": {
        "key5": "value5"
      }
    }
  },
  "group1": {
    "baz": {
      "left": "bas",
      "right": "bars"
    },
    "nest": {
      "left": {
        "key": "value"
      },
      "right": "str"
    }
  },
  "group2": {
    "left": {
      "abc": 12345,
      "deep": {
        "id": 45
      }
    }
  },
  "group3": {
    "right": {
      "fee": 100500,
      "deep": {
        "id": {
          "number": 45
        }
      }
    }
  }
}

Answer №2

In tackling this issue, there are several approaches to consider depending on your coding style and personal preferences.


Hoisting

An effective technique worth exploring is hoisting, where variables are declared at the beginning of a file and used later. Specifically with functions, defining them traditionally using the function keyword ensures their placement before other variables in the scope.

For example:

test();

function test() {
  console.log("Hello! :)")
}


Currying

Personally, I find currying to be quite valuable, especially when working with React as discussed here. Currying involves having a method that accepts one argument and returns a result or function that also takes one argument, creating a chain effect.

An illustration of how this can be implemented:

const fn1 = (arg) => (callback) => {
  if (callback === undefined || callback === null) {
    console.log(arg);
  } else if (typeof callback === "function") {
    callback(arg);
  } else {
    throw new Error("The callback argument must be defined, fn1(arg)(callback).");
  }
}

// More code examples...

Alternatively, utilizing a callback function without currying could also be a feasible solution, particularly by passing in an object with keys for desired functionality.


Class Syntax

An additional straightforward approach involves defining all functions within a class, enabling static/stateless organization. This method allows for simpler and more organized code structure regardless of order, especially beneficial when combined with TypeScript for private method definitions.

Example implementation:

class Test {
  run () {
    this.print();
  }

  print() {
    console.log("I ran!");
  }
}

new Test().run();

Consider your ESLint setup and coding style preference when selecting the most suitable solution for your needs.


Conclusion

Ultimately, the choice of approach depends on individual coding style, values, and team dynamics. There is no one-size-fits-all solution to this problem, so it's important to choose what aligns best with your requirements.

If you have additional insights to contribute, feel free to edit or comment. Different perspectives are welcome in discussing such matters with no definitive answer. Explore the use of currying in functional programming for added complexity!

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

Error: 'fs' module not found in React.js and cannot be resolved

Encountering issues with tatum io v1 + react? Developers have acknowledged that it's a react problem which will be addressed in V2. In the meantime, you can utilize tatum io V1 with node js. I've included all dependencies that could potentially ...

Socket.io is most effective when reconnecting

I am currently in the process of developing a React application that connects to a Node.js API and I am trying to integrate the Socket.io library. Following various online tutorials, my code now looks like this: API: import express from 'express&apo ...

React Native: Issue with Higher Order Component when utilizing the `onLayout` prop

The functionality of this component involves rendering the Placeholder. Once it acquires its layout, this higher-order component (HOC) updates the state and invokes the child component with the values. Everything seems to be working fine up to this point. ...

Utilizing icons with vuetify's v-select component: a guide

In the code snippet below, I am using a v-select element to display a list of items filled from an array: <v-select v-model="myModel" :items="users" chips :readonly="!item.Active" label="Required users to f ...

What could be causing the RTCPeerConnection icegatheringstatechange to not function properly?

I have been trying to utilize the icegatheringstatechange event handler, but it doesn't seem to be functioning properly. Is there a different method I can use to monitor changes in icegatheringstate? How can I determine when all ice candidates have be ...

New methods for Sequelize ES6 models do not currently exist

Encountering issues while using Sequelize JS v4 with ES6 classes, I'm facing difficulty with the execution of instance methods. Despite being defined in the code, these methods appear to be non-existent. For instance - Model File 'use strict&a ...

The click event in jQuery/JavaScript is not functioning

Although it should be as simple as breathing, I just can't seem to spot the mistake in my code... I attempted to add a click event to a div with a unique ID, but unfortunately, it's not working at all. This issue persists with both jQuery and Ja ...

How can I utilize the JQuery GetJSON function to retrieve HTML content from an external webpage?

Imagine you're attempting a jQuery ajax request like this: $.ajax({ ... url: http://other-website.com ... }) You probably know that due to the same-origin policy, this request will fail because the URL is for an external domain. But the ...

The React hook is activated each time a key is pressed, but it should ideally only be triggered when there is

I have created a hook for my Formik form that should be called whenever a specific field's Selection option is chosen. However, I am facing an issue where the hook is being triggered on every key press in every form field. I am not sure what mistake ...

What is the best way to obtain the parent of a recursive array structure?

Here is an example of an array structure: $navArray = array( array( 'id'=>1, 'text'=>'1A', 'href'=>'1a', 'childs'=>array( array( ...

I developed an RPG game with an interactive element using jQuery. One of the biggest challenges I'm facing is the random selection process for determining which hero will be targeted by the enemy bots during battles

Hello, this marks my debut on stack overflow with a question. I've created a game inspired by old school RPGs where players choose from three heroes based on the Marvel universe to battle one of three enemies. The problem I'm facing is that even ...

Looking to retrieve the key value or iteration of a specific item within an Angular UI tree?

Here is a snippet of code showcasing how to use angular-ui-tree: <div ui-tree> <ol ui-tree-nodes="" ng-model="list"> <li ng-repeat="item in list" ui-tree-node> <div ui-tree-handle> {{item.title}} </div& ...

Struggling to access the html elements within a component once the ng2 page has finished loading?

I am working on a web app that utilizes ng2-smart-table and I want to hide all cells within the table. However, when attempting to retrieve all the cells using document.getElementsByTagName("td") in the ngAfterViewInit() lifecycle hook, I noticed that the ...

Is there a way to make Express.js pass parameters with special characters exactly as they are?

I am currently working on a project within the freeCodeCamp "API and Microservices" curriculum that involves using Express.js to handle routes. The project itself is relatively straightforward, with some pre-defined routes and others that need to be creat ...

Preventing Time Limit Reset on Page Refresh with Jquery/Javascript

I'm encountering an issue with the time limit on my online quiz program. Whenever the page is reloaded or refreshed, the time limit resets. I have implemented this time limit in my online quiz program using a PHP header('Location:') to prog ...

How can the Header of a get-request for an npm module be modified?

Currently, I am utilizing an npm module to perform an API request: const api_req = require('my-npm-module-that-makes-an-api-request'); However, I am seeking a way to modify the user-agent used for requests generated internally by the npm module ...

What is the best way to save a variable once data has been transferred from the server to the client

When I send the server side 'data' to the client, I'm facing an issue where I can't store the 'data' into a variable and it returns as undefined. What could be causing this problem and how can I fix it? The request is being ...

ng-bind-html is having trouble parsing the HTML correctly and binding it

Here is the code for my controller: myApp.controller('actionEditController', ['$scope', '$stateParams', '$sce',function ($scope, $stateParams, $sce) { $scope.table="<p>OOPSY</p>"; $sc ...

Creating a personalized news feed using getstream.io in Node.js - a step-by-step guide

const stream = require('getstream'); // Setting up a newsfeed stream using getstream const client = stream.connect( null, ); // Defining a feed for user1 var user1 = client.feed('user', 'user1'); // Adding a custom activity ...

The onClick function within the .map function is continuously triggered

I am encountering an issue with my code where a Dialog confirmation prompt from Material UI keeps getting called unexpectedly. The problem seems to arise when I add a value to the function that is triggered by a button click within a loop of an array usi ...