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

The response data from Axios remains undefined even after a successful request

function verifyPassword() { let pass = document.getElementById("password").value let emailAddr = document.getElementById("email").value Service.confirmLoginPassword(emailAddr, pass).then(response => { result = re ...

How can I simply show a specific value from an object in Vue?

I've created a Vue application that filters messages and displays them on a page. Currently, when a user selects a message from the list, the entire JSON data associated with that message is shown on the page. However, I want to only display a specifi ...

What is the best way to incorporate tooltips in SVG?

My teacher wants me to display a tooltip on an SVG circle with links and information when we hover over it. They suggested using jQuery UI, but I've done extensive research and nothing has been able to assist me so far. ...

Tips for triggering an error using promise.all in the absence of any returned data?

I'm dealing with an issue in my project where I need to handle errors if the API response returns no data. How can I accomplish this using Promise.all? export const fruitsColor = async () : Promise => { const response = await fetch(`....`); if( ...

Guide to activating a function by tapping anywhere on a webpage in iPhone Safari

I am attempting to implement a jQuery function that will change the text of a div in the iPhone browser when any area of the page is clicked. Here is my current setup: <div id="alternative_text">traduit</div> <script type="text/javascript ...

Minimizing Jitter in HTML5 and JavaScript Applications

I am currently working on developing a metronome as a hobby using JavaScript/HTML5 with the intention of turning it into a FirefoxOS app. One issue I've encountered is Jitter, which is unacceptable for Metronomes. As JavaScript is single-threaded and ...

The jQuery validation applied to the data submission per post is not functioning as expected as the $_POST variable is appearing empty

I'm facing an issue with Jquery, AJAX, and field validation. Although the validation and redirect are working fine, I'm having trouble retrieving values from fields in the $_POST array. Below is the start tag of the form: <form class="smart ...

Generating a dynamic form by utilizing a JavaScript JSON object

I need assistance with creating an html form based on a JSON object’s properties. How can I target multiple levels to generate different fields and also drill down deeper to access field details? I am open to suggestions for alternative formats as well. ...

Preview and crop your image before uploading

Currently, I am working on developing a form that will enable administrators to upload an image. The aim is to allow them to preview the image before uploading it, displaying it at a specific size and providing the option to click on the image to open an i ...

There seems to be an issue with the HighCharts chart export feature as it is not showing the Navigator graph

We are currently using HighCharts version 4.2.2 http://api.highcharts.com/highcharts/exporting While going through their exporting documentation, I made a decision to not utilize their default menu dropdown. Instead, I only needed access to the .exportCh ...

We apologize, but the module you are looking for cannot be found: Unable to locate 'fs'

Trying to create a new MDX blog website using Next.js 13 with the latest app router, but encountering an error message saying "Module not found: Can't resolve 'fs'". It was working fine in Next.js 12 and pages directory, but not in the lates ...

What is the reason behind allowing JavaScript to perform mathematical operations with a string type number?

let firstNum = 10; let secondNum = "10"; console.log(firstNum * secondNum); // Result: 100 console.log(secondNum * secondNum); // Result: 100 ...

Struggling to locate the element for clicking or sending keys in Selenium using Java?

Hi everyone, I'm a new member of this forum and I'm just starting out with coding for the first time. I have encountered an issue with a specific part of my code: md-input-container class="md-default-theme md-input-invalid">> label for="i ...

What is the best way to create a CSS class for a list element in React?

I am facing an issue with styling buttons in my UI. These buttons represent different domains and are dynamically generated based on data fetched from the server using the componentDidMount() method. Since I do not know the quantity of buttons at the time ...

Use jQuery to swap out every nth class name in the code

I am looking to update the 6th occurrence of a specific div class. This is my current code <div class="disp">...</div> <div class="disp">...</div> <div class="disp">...</div> <div class="disp">...</div> < ...

Issue with Type-Error when accessing theme using StyledComponents and TypeScript in Material-UI(React)

I attempted to access the theme within one of my styled components like this: const ToolbarPlaceholder = styled('div')((theme: any) => ({ minHeight: theme.mixins.toolbar.minHeight, })); This information was found in the documentation: htt ...

Linking chained functions for reuse of code in react-redux through mapStateToProps and mapDispatchToProps

Imagine I have two connected Redux components. The first component is a simple todo loading and display container, with functions passed to connect(): mapStateToProps reads todos from the Redux state, and mapDispatchToProps requests the latest list of todo ...

Unable to access a hyperlink, the URL simply disregards any parameters

When I click an a tag in React, it doesn't take me to the specified href. Instead, it removes all parameters in the URL after the "?". For example, if I'm on http://localhost:6006/iframe.html?selectedKind=Survey&selectedStory=...etc, clicking ...

Exploring different sections of the website

I am looking to create a seamless navigation experience between controls on a webpage. Below is an example code snippet that outlines the functionality where a user can click on any component and be directed to another controller. Sample code: const app ...

Using a jquery function within a Laravel view

I am trying to retrieve a selected item from a dropdown menu using jQuery and then redirect it to a controller function. This function will return some data to be displayed based on the selected item. I could really use some assistance with this. Here is m ...