Tips for creating an efficient, versatile, and error-proof function using Javascript

There is an Object that can take on three different forms:

{done, key, value}
{done, key}
{done, value}

When two of these Object instances are passed to a function, it needs to handle all three cases like a logical or operation.

This is the approach taken so far:

const orFunction = (predicate, defaultValue) => input1 => input2 => {
  const result1 = predicate(input1),
    result2 = predicate(input2);

  return !result1 && !result2 ? defaultValue
    : result1 && result2 ? [input1, input2]
    : result1 ? input1
    : input2;
};

const entry = {key: 1, value: "a"};
const value = {value: "a"};
const key = {key: 1};

orFunction(x => x !== undefined, []) (entry.key) (entry.value); // ["a",1]
orFunction(x => x !== undefined, []) (key.key) (key.value); // 1
orFunction(x => x !== undefined, []) (value.key) (value.value); // "a"
orFunction(x => x !== undefined, []) (none.key) (none.value); // []

This solution works for the current problem, but it raises the question of whether it can be generalized to other scenarios. Is it a type-safe and functional programming-friendly solution?

Answer №1

When considering type safety in Javascript, it's important to note that it is an untyped language. However, even in this context, thinking about types can help improve the reliability and clarity of your code.

Prioritize Error Handling

Since Javascript lacks a compiler to catch bugs before deployment, it's crucial to prioritize error handling in your code:

It's better to throw errors explicitly rather than silently ignore them.

In your function orFun, you are working with an inclusive or type, which can lead to issues due to the unrestricted codomain. It's recommended to be transparent and throw errors when necessary, rather than returning a default value like null which would require additional checks by the caller.

// Example of orFun implementation and usage
const orFun = p => x => y => {
  const r = p(x),
    s = p(y);

  if (!r && !s)
    throw new TypeError();

  return r && s ? [x, y]
    : r ? x
    : y;
};

const entry = {key: 1, value: "a"},
  none = {};

orFun(x => x !== undefined) (entry.key) (entry.value); // [1, "a"]
orFun(x => x !== undefined) (none.key) (none.value); // throws TypeError

Explicitly Define Cases

Another issue in your code is that opFun returns three different types:

Number
String
[Number, String]

It's advisable to make the return types explicit to avoid ambiguity. One approach is to use a union type encoding to enforce all cases to be handled by the caller, which improves code clarity and robustness.

Implementing this style not only avoids conditional statements on the calling side but also makes your code more resilient and expressive.

The technique described above essentially involves pattern matching through continuation passing (CPS), allowing you to select the appropriate continuation based on the case at hand, debunking the notion that Javascript lacks pattern matching capabilities.

Enhancing with a Generic Implementation

To create a more versatile implementation, consider a generalized operation that constructs a These value, accommodating scenarios where the type might not align with expectations. This operation, named toThese, allows for greater flexibility by accepting two predicate functions and enabling error handling with default values if needed.

// Sample implementation of toThese and its usage
const toThese = (p, q, def) => x => y =>
  _let((r = p(x), s = q(y)) =>
    r && s ? these(x, y)
      : !r && !s ? def(x) (y)
      : x ? _this(x)
      : that(y));

const isDefined = x => x !== undefined;

const o = {key: 1, value: "a"};
const p = {key: 1};
const q = {value: "a"};
const r = {};

const tx = toThese(isDefined, isDefined, x => y => {throw Error()}) (o.key) (o.value),
  ty = toThese(isDefined, isDefined, x => y => {throw Error()}) (p.key) (p.value),
  tz = toThese(isDefined, isDefined, x => y => {throw Error()}) (q.key) (q.value);
  
let err;

try {toThese(isDefined, isDefined, () => () => {throw new Error("type not satisfied")}) (r.key) (r.value)}
catch(e) {err = e}

// Output the results and handle any errors
console.log(tx.runThese(x => x + 1,
  x => x.toUpperCase(),
  (x, y) => [x + 1, y.toUpperCase()]));

console.log(ty.runThese(x => x + 1,
  x => x.toUpperCase(),
  (x, y) => [x + 1, y.toUpperCase()]));

console.log(tz.runThese(x => x + 1,
  x => x.toUpperCase(),
  (x, y) => [x + 1, y.toUpperCase()]));
  
  throw err;

Answer №2

One interesting aspect of a function in functional programming is that it can potentially return different types of values:

  • [String, Number]
  • String
  • Number

An attempt to address this variability is made in the following code by using tagged sums.

However, the act of unwrapping the results could lead to design issues. How would you handle each distinct output (or type) once it is unwrapped? In this code snippet, the function unwrapObjectKind simply outputs the values. In a real-world scenario, a partially-applied cata function would be used to perform specific actions based on each tag.

// #1 Define the tagged sum type to represent the cases
const ObjectKind = {
   keyAndValue: x => ({ tag: 'keyAndValue', x }),
   key: x => ({ tag: 'key', x }),
   value: x => ({ tag: 'value', x }),
   none: x => ({ tag: 'none', x })
}

// #2 Encode the cases and assign tags
const toObjectKind = x => {
   if (x != null && typeof x == 'object') {
     if (x.hasOwnProperty ('key') && x.hasOwnProperty ('value')) 
         return ObjectKind.keyAndValue ([x.key, x.value])
     else if (x.hasOwnProperty ('key')) 
         return ObjectKind.key (x.key)
     else if (x.hasOwnProperty ('value'))
         return ObjectKind.value (x.value)
   }
         
   return ObjectKind.none ([])
}

// #3 Tag the cases
const caseA = toObjectKind ({ key: 'a', value: 1 })
const caseB = toObjectKind ({ key: 'b' })
const caseC = toObjectKind ({ value: 3 })
const caseD = toObjectKind (null)


// #4 Perform catamorphism to match cases and take corresponding actions
const cata = matches => ({ tag, x }) => 
      matches[tag] (x)

// The identity combinator, used for unwrapping the cases
const I = x => x

// #5 Unwrap any of the tagged object kinds.
const unwrapObjectKind = cata ({ 
   keyAndValue: I,
   key: I,
   value: I,
   none: I
}) 

const outputA = unwrapObjectKind (caseA)
const outputB = unwrapObjectKind (caseB)
const outputC = unwrapObjectKind (caseC)
const outputD = unwrapObjectKind (caseD)

console.log ('outputA:', outputA)
console.log ('outputB:', outputB)
console.log ('outputC:', outputC)
console.log ('outputD:', outputD)

Answer №3

One way to approach this is by implementing type checking for specific functions.

Here is a general approach to type checking:

const checkType = (function() {
  const TypePattern = {
    Date: /Date/,
    Null: /Null/,
    Number: /Number/,
    Object: /Object/,
    RegExp: /RegExp/,
    String: /String/,
    Undefined: /Undefined/
  };

  /**
   * This function checks if the arguments passed to a function match the expected types.
   *
   * @param Array.<*> arguments List of arguments passed to the function.
   * @param Array.<{ name: String, type: String }> argumentsMeta Meta information about expected argument types.
   */
  function checkType(arguments, argumentsMeta) {

    return argumentsMeta
      .find(
        (meta, index) => {
          return !TypePattern[meta.type]
            .test(
              Object.prototype.toString.call(arguments[index])
            )
        }
      );
  }

  return checkType;
})();

You can use the checkType function in this way:

const myFunc = function myFunc(paramA /* should be a string */ , paramB /* should be an object */ ) {
  if (checkType(Array.from(arguments), [{
      name: "paramA",
      type: "String"
    }, {
      name: "paramB",
      type: "Object"
    }])) {
    return new Error("Invalid arguments detected");
  }

  // Function logic here
};

If you prefer throwing errors, you can extend the checkType function like this:

function assertNoTypeMismatch() {
  const foundMismatch = checkType.apply(null, arguments);

  if (!foundMismatch) {
    return;
  }

  throw new Error(`TypeMismatch: parameter ${foundMismatch.name} should be of type ${foundMismatch.type}`);
}

Now, you can update your function accordingly:

const myFunc = function myFunc(paramA /* should be a string */ , paramB /* should be an object */ ) {
  assertNoTypeMismatch(Array.from(arguments), [{
      name: "paramA",
      type: "String"
    }, {
      name: "paramB",
      type: "Object"
    }])

  // Function logic here
};

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

Managing simultaneous access to a variable in NodeJS: Best practices

For instance: var i = 0; while(true) http.request('a url', callback_f); function **callback_f**(){ **i++**; } In this straightforward scenario, multiple requests could unintentionally increase the value of i simultaneously. How can I creat ...

Importing JavaScript into an Angular component: A beginner's guide

Within my Angular-11 project, I have included the following JavaScript file: "node_modules/admin-lte/plugins/bs-stepper/js/bs-stepper.min.js", I have added it to the angular.json configuration as detailed above. import Stepper from '.. ...

How can JavaScript be used to modify the locale of a web browser?

Is it possible to programmatically set the window.navigator.language using AngularJS? I am exploring different methods to achieve this. At the moment, I rely on a localization service to handle my i18n localization switching. ...

Discovering the total number of tickets based on priority in an array with Javascript

I have the following data set { agent_id:001, priority:"High", task_id:T1 }, { agent_id:001, priority:"High", task_id:T1 }, { agent_id:001, priority:"Medium", task_id:T1 } { agent_id:002, priority:"High", task_id:T1 ...

Conceal the div element five seconds after the registration process is completed

Is it possible to automatically hide a div 5 seconds after a user registers? Using the timestamp in PHP for the user's registration, there may be a way to achieve this with jQuery, but it's not certain. I found a script online that successfully ...

AngularJS allowing multiple ngApps on a single page with their own ngRoute configurations, dynamically loaded and initialized

Seeking advice on lazy loading separate ngApps on one page and bootstrapping them using angular.bootstrap, each with its own unique ngRoute definitions to prevent overlap. I have a functioning plunkr example that appears to be working well, but I am unsur ...

Changing the color of HTML text based on a variable in Vue JS can be achieved by altering the CSS styles dynamically

I have developed a website that shows values with a set threshold. I now want to change the color of the values when they exceed the threshold, but I am unsure of how to proceed. I want to display consultation.indicateurs.TRS in red when it exceeds the va ...

Automatically scrolling down a div as new content is added using XMLHTTPRequest.openConnection

https://jsfiddle.net/kv5gbamg/ - I created a jsfiddle to demonstrate the functionality of the system Essentially, I am seeking a way to automatically scroll the scrollbar to the bottom every time a new message is received. My system updates the div with ...

Constructing a table in React using columns instead of the traditional rows

Currently tackling a project in react, I am looking to construct a material-ui table with specific characteristics. Within my array of elements, each element represents a column in the table and contains a name and the number of cells it covers. For examp ...

Creating a custom autocomplete search using Angular's pipes and input

Trying to implement an autocomplete input feature for any field value, I decided to create a custom pipe for this purpose. One challenge I'm facing is how to connect the component displaying my JSON data with the component housing the autocomplete in ...

What is the best way to incorporate a new attribute into an array of JSON objects in React by leveraging function components and referencing another array?

Still learning the ropes of JavaScript and React. Currently facing a bit of a roadblock with the basic react/JavaScript syntax. Here's what I'm trying to accomplish: import axios from 'axios'; import React, { useState, useEffect, useMe ...

Eliminate the prefixes of object keys in Node.js

This is my first time reaching out for help on a forum. I've been struggling with an issue for days and haven't been able to find a solution. It all started with a database query that returned the following data: { "prod_format": "400 ml", "pro ...

Editing an XML file on the browser and saving it in its original location on the local file system

Seeking assistance with editing an XML file directly from the browser on a local file system and saving it back to its original location. I have conducted extensive research using Google, but have not been able to find a suitable solution. Any help or gu ...

Exploring the Depths of Vue Router: Incorporating Dynamic Nested Routes

Hey everyone, I'm currently diving into working with the vue-router and Vuex Store. I'm facing a challenge where I have a route that consists of two dynamic parameters (:id, :templateId). My question is, what do I need to specify in my routes c ...

Can Ember store in Route handle Post requests?

Is there a way to use ember store functionality similar to this.store.findAll('report') for making POST requests with postObj in my route? Also, how should I handle the response received from these requests? Right now, I am sending ajax POST requ ...

The overflow hidden property does not seem to be effective when used in conjunction with parallax

The issue arises when I attempt to use the overflow hidden property in combination with parallax scrolling. Although everything seems to be working correctly with JavaScript for parallax scrolling, setting the overflow to hidden does not contain the image ...

Tips on how to modify a select option in vue based on the value selected in another select

My Web Api has methods that load the first select and then dynamically load the options for the second select, with a parameter passed from the first selection. The URL structure for the second select is as follows: http://localhost:58209/api/Tecnico/Tanq ...

Refresh the page with user input after a button is clicked without reloading the entire page, using Python Flask

My python/flask web page accepts user input and returns it back to the user without reloading the page. Instead of using a POST request, I have implemented Ajax/JavaScript to handle user input, process it through flask in python, and display the result to ...

Iterate through a JavaScript array to access objects with varying IDs

Struggling to navigate the JSON data due to ID 29450 and 3000 out of a total of 1500 IDs in the database. Looking to log the information ['Id', 'Description', 'StartDate'] for both IDs. Feeling a bit stuck, hoping someone can ...

The Less compiler (lessc) encounters an issue on a fresh operating system. ([TypeError: undefined is not a function])

After setting up my new development environment on Windows 10, I encountered an issue with less. Following the instructions on lesscss.org, I installed less using: npm install -g less The installation process completed without any errors. However, when ...