What is the optimal parameter order when utilizing pre-curried functions and composition in JavaScript?

We have a simple, mathematically curried function for subtracting numbers:

function sub(x) {
  return function (y) {
    return x - y;
  };
};

sub(3)(2); // 1

The function signature matches the obtained result. However, when function composition comes into play, things change:

function comp(f) {
  return function (g) {
    return function (x) {
      return f(g(x));
    };
  };
};

function gte(x) {
  return function (y) {
    return x >= y;
  };
};

comp(gte(2))(sub(3)) (4); // true

In function composition, the last parameter of each function is crucial as it receives the value returned by the previous function. In our example, 4 - 3 >= 2 would give false. The actual computation is: 2 >= 3 - 4, resulting in true.

To achieve the desired outcome, we can easily tweak sub and gte:

function sub(y) {
  return function (x) {
    return x - y;
  };
};

function gte(y) {
  return function (x) {
    return x >= y;
  };
};

comp(gte(2))(sub(3)) (4); // false

Now, directly calling functions gives unexpected values:

sub(3)(2); // -1 (expected 1)
gte(2)(3); // true (expected false)

We could switch arguments for each call or create partially applied functions:

function flip(f) {
  return function (x) {
    return function (y) {
      return f(y)(x);
    };
}

flip(gte)(2)(3); // false

var gteFlipped = flip(gte);
gteFlipped(2)(3); // false

Both approaches are cumbersome and not very readable.

Is there a preferred parameter order? Or a way to use both, depending on requirements (similar to Haskell's left/right sections for operators)?

A solution must consider that only unary functions are used!

Answer №1

If you're looking to partially apply operators without the need for verbose code like:

var gte2 = function (x) { return x >= 2; };

That's a valid use case, focusing on "partially applying operators".

The solution is straightforward. Simply create a curried function. Here's an example:

// var gte = y => x => x >= y; // ES6 syntax

var gte = function (y) {
    return function (x) {
        return x >= y;
    };
};

var gte2 = gte(2);

There are actually two ways to perform partial application with binary operators:

  1. Partially apply the operator to the left argument.
  2. Partially apply the operator to the right argument.

This leads us to two key questions:

  1. Which argument should be defaulted when partially applying the operator?
  2. How can we partially apply the operator to the other argument?

One thing we can agree on is that providing both arguments to the operator doesn't make sense.

// Instead of writing:

add(2)(3)

// You can simply write:

2 + 3

We create curried operator functions primarily for partial application.

Therefore, providing both arguments to the function simultaneously is counterintuitive.

What does this mean in practice? It implies that:

  1. We have the flexibility to choose any argument order.

    // Both options are valid:
    
    var sub = x => y => x - y;
    
    // And:
    
    var sub = y => x => x - y;
    
  2. The function only needs to make sense with one argument.

    // For instance:
    
    var sub = y => x => x - y;
    
    // This works:
    
    sub(1) // interprets as (x => x - 1)
    
    // However, this doesn't work intuitively:
    
    sub(2)(3) // expected (2 - 3) but it calculates (3 - 2)
    
    // Yet, it only needs to make sense given one argument.
    

So, which argument order is preferable? It all depends.

  1. For commutative operations, argument order is irrelevant.

    Both addition and multiplication, for example, are commutative. Hence, a + b = b + a and a * b = b * a.

  2. Non-commutative operations typically benefit from a right-to-left argument order as it enhances readability during partial application.

    For instance, lt(2) usually means x => x < 2, not x => 2 < x.

    Why is this common? In JavaScript, function names precede the argument, so name(arg) reads naturally as

    x => x name arg</code rather than <code>x => arg name x
    .

  3. Though there are exceptions to the second guideline. Notably, division:

    div(10) // suggests divide 10 by x
            // not divide x by 10
    

    Determining the correct argument order for such cases may vary, though left-to-right seems more intuitive to me.

Here are several curried operator functions to consider:

// Commutative operators:

var add = x => y => x + y;
var mul = x => y => x * y;

// Right-to-left operators:

var lt  = y => x => x < y;
var gt  = y => x => x > y;
var lte = y => x => x <= y;
var gte = y => x => x >= y;
var sub = y => x => x - y;

// Left-to-right operators:

var div = x => y => x / y;

Now, how do we partially apply these operators to the "other" argument?

The sole approach involves creating a new function with reversed argument orders.

Fortunately, creating new functions for every operator isn't necessary:

  1. For commutative operators, the argument order is interchangeable. Therefore:

    flip(add) = add
    flip(mul) = mul
    
  2. Relational operators don't require extra functions either:

    flip(lt)  = gt
    flip(gt)  = lt
    flip(lte) = gte
    flip(gte) = lte
    
  3. Only flipped operator functions for sub and div are essential:

    var subFrom = x => y => x - y; // subFrom(5) corresponds to (y => 5 - y)
    var divBy   = y => x => x / y; // divBy(10) represents (x => x / 10)
    

In conclusion, trust your intuition when determining the best course of action.

Answer №2

After reviewing your composition, here is my interpretation:

comp(gte(2))(sub(3)) (4);

gte(2) = function(y) { return 2 >= y; } // (x = 2)
sub(3) = function(y) { return 3 - y; } // (x = 3)

// Therefore:
comp(gte(2))(sub(3)) = function(x) {
    var f = function(y) { return 2 >= y; };
    var g = function(y) { return 3 - y; };
    return f(g(x));
};

// Call with (x = 4):
x = 4
g(4) = 3 - 4 = -1
f(-1) = (2 >= -1) = true

In summary, it appears that your assumptions may be incorrect. There could be a misunderstanding on your part, but pinpointing it exactly is challenging for me. I believe that the approach taken in this JavaScript code is unnecessarily complex and can lead to confusion, but ultimately, it's just my perspective.

Answer №3

This reply is based on Aadit's response.

In the world of Javascript, there exists a necessity for fully applied curried operator functions, especially when they are treated as First Class Citizens:

function between(ops) {
  return function (left) {
    return function (right) {
      return function (n) {
        // Utilizing native Javascript operators here
        // However, passing these operators to a function poses a challenge due to them not being First Class.
        return ops[0](left)(n) && ops[1](right)(n);
      };
    };
  };
}

function lte(y) { return function (x) { return x <= y; }; }
function gt(y) { return function (x) { return x > y; }; }

between([gt, lte])(2)(4)(4); // true
// Evaluates as: gt(2)(4) && lte(4)(4) === true; (confusing)

The between function may seem absurd, but it demonstrates that there is practicality in employing fully applied curried operator functions within Javascript. There are likely numerous other scenarios where this technique could be beneficial.

Aadit rightly points out that something like sub(2)(3) goes against the core concept of currying!

So, what would a viable solution entail?

  1. All curried operator functions must adhere to a right-to-left argument order
  2. An additional function should be introduced to indicate atypical usage when supplying all arguments to a curried function simultaneously

Introducing uncurryOp:

// Designed for all operator functions
function uncurryOp(f) {
  return function (x, y) {
    return f(y)(x);
  };
}

uncurryOp(gt)(2, 4); // false (intuitive)

While this solution isn't ideal, I believe there isn't one due to the absence of First Class and partially applicable operators in Javascript.

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

Angular $resource encounters a 400 Bad Request error when attempting a PUT request, triggering the $resolve and $promise

My service is structured as follows (with variables removed): angular .module('app') .factory('Employee', function($resource) { return $resource("https://api.mongolab.com/api/1/databases/:dbName/collections/:collectionN ...

What is the process for accessing a local .json file from a remote machine or folder?

I am currently working on a project that involves a .json file stored in my local folder. Along with the file, I have Javascript code in the same directory to access and read the values from the .json file. To open the file, this is the line of code I use: ...

I have experimented with both POST and GET methods in Node.js, exploring a variety

After creating a login page with a GET form, the server code includes: app.use(express.static(path.join(__dirname, 'public'))); I am facing an issue trying to implement a POST request. When I use "POST", it gives me a "CANNOT / PO ...

Indeed, verifying parent.parent access

Currently, I am utilizing the yup module to validate my form. My objective is to access the parent in order to test the value. Below is my schema: enabled: yup.boolean(), contactDetail: yup.object().shape({ phoneNumber1: yup.string().nullable(), pho ...

Having trouble with errors when adding onClick prop conditionally in React and TypeScript

I need to dynamically add an onClick function to my TypeScript React component conditionally: <div onClick={(!disabled && onClick) ?? undefined}>{children}</div> However, I encounter the following error message: Type 'false | (() ...

Utilize vue.js to save the components of an object

data() { return: { user: {}, userName: "", userAge: "" } }, methods: { saveUserName: function() { this.userName = this.newUserName; this.$refs.userNameModal.hideModal(); this.$ ...

Removing a Request with specified parameters in MongoDB using NodeJS

Working with Angular 4 and MongoDB, I encountered an issue while attempting to send a delete request. My goal was to delete multiple items based on their IDs using the following setup: deleteData(id) { return this.http.delete(this.api, id) } In order ...

Looking to incorporate multiple accordion drop down menus on your website? Utilize a combination of HTML, CSS, and JavaScript to

I am experiencing a challenge with implementing multiple accordion menus on my website. Whenever I attempt to duplicate the code, the new accordion menu appears but clicking on the first bar simply scrolls me back to the top of the webpage. Below is the H ...

Is the CSS Transition Solely Active for the Introductory Animation?

I'm currently looking to enhance the smoothness of div expansion and contraction on hover using CSS transitions. However, I have noticed that the Transition property only seems to affect the entry animation (i.e., when the mouse hovers and the div exp ...

JQuery jqx validation is experiencing some issues

Utilizing jquery plugins and widgets from jqx for basic form validation in my ruby-on-rails application has proven to be very helpful. Here is a simple example of an HTML form: <form id="newForm"> <input type="text" id="name"/> < ...

JQuery addClass function not functioning properly when used in conjunction with an AJAX request

I have a website where I've implemented an AJAX pagination system. Additionally, I've included a JQUERY call to add a class to certain list items within my document ready function. $(document).ready(function(){ $(".products ul li:nth-child(3 ...

Loading external scripts prior to component loading in Vue.js

Within my Vue project, I have the need to fetch a script from a server location (e.g. https://myurl.com/API.js). This script contains a variable that I intend to utilize within my Vue component/view. The issue arises when I attempt to load this script usi ...

Tips for leveraging stage 3 functionalities in TypeScript?

Array.prototype.at() is currently in the proposal stage 3. Even after adding "lib": ["ESNext"] to my tsconfig.json, I encountered the error: Property 'at' does not exist on type 'number[]'. Could you shed some light ...

Unable to install react-dom/test-utils using npm

I recently included two libraries in my package.json "devDependencies": { ... "react-dom/test-utils": "*", "react-test-renderer/shallow": "*" }, These were recommended by the React documentation to align with version 16 of the React ecosy ...

Creating an Angular table using reactive forms: a step-by-step guide

After reviewing the HTML snippet provided below, it is evident that there is a table with looping through mat cell using *matCellDef="let model". Inside each cell, there are input fields which are reactive forms. Each row or cell needs to have it ...

Changing the ng-src attribute with a custom service in an AngularJS application

Check out this Pluker I created for making image swapping easier. Currently, the images swap normally when coded in the controller. However, I am interested in utilizing custom services or factories to achieve the same functionality. Below is the code snip ...

Uncovering unseen tags generated by JavaScript on a webpage using Python

I have THIS LINK page that contains javascript. To view the javascript, simply click on show details. How can I extract data from this URL source? Should I use re? Here is what I attempted with re: import urllib import re gdoc = urllib.urlopen('Tha ...

What are the steps to save data on a user's computer through a web browser?

Is it feasible to save data in the client's computer using a web browser and jQuery code to interact with the file system? ...

Tips for assigning a default value when an error occurs

Currently diving into the world of React and experimenting with rendering 10 pieces of data from a specific API. After crafting a function to iterate through the fetched information, extracting the title and image has been quite the challenge: for (let ...

How to implement variables within the .map() function in JavaScript?

I am working on a map function where I need to pass in a variable as a Key to change the object item key to be based on that variable instead. For example, I want the obj.Salary below to actually represent the salary value when day equals "Day" instead o ...