Food for thought

Food for thought
0.0 0

#1

This is a very important concept that keeps showing up in different JS libraries I wish I learned it earlier so I urge all of you to think about this challenge.

If you have a function that has n arguments how would you write a function that takes that function as an argument and returns the function that does the same job when is called as a chain of n functions, each taking a single argument.

so let’s say you have:

function add (a ,b, c){
   return a + b + c
} 

and you have to write a mysterious function that takes add function as an argument and returns the function that will give the same result

var addOneAtTheTime = mysterious(add);
add(1,2,3) === addOneAtTheTime(1)(2)(3)

#2

That’s a really interesting challenge, and one that I hadn’t thought about. Do you already have a solution? Not that I want you to spoil it, I’m just curious.

Also, you’ve got a syntax error:

function add (a + b + c){
   return a + b + c
}

should be

function add (a, b, c){
   return a + b + c
}

#3

Thank you for noticing syntax error. The solution for this is a common practice and many libraries have a function that does the same thing, for example, loadash have one.
But I think it’s something that is interesting to think about and explore, with a rise of functional programming it is one of the patterns I keep seeing around


#4

I’m not sure if this is related to the challenge, but I remember seeing something similar on toptals 25 essential questions asked in an interview. Basically you are asked to write an add function where both of these cases work:

add(2, 3)
add(2)(3)

The concept being tested here is closure. I returned a function that preserved the first argument. I’m not sure if the same logic would work here since it’s an indefinite number of arguments. This one is already giving me a headache.


#5

This is similar but not exactly the same. In your example, you know that add(2) needs to return a function that takes one value and returns that value plus the value stored in the closure. In this case you accept a generic function that could have any number of parameters.

After some time I was able to come up with this - Spoiler!!

function mysterious(func) {
    function returnFunc(x, args) {
        if (!args) {
            args = [];
        }
        var funcVal = func(...args, x);
        if (!isNaN(funcVal)) {
            return funcVal;
        }
        args.push(x);
        return function (y) {
            return returnFunc(y, args);
        }
    }
    return returnFunc;
}

This is based on my observation that a function will return NaN if it doesn’t get the args it needs to return a value.
Oddly NaN === NaN returns false :confused:


#6

Here’s my attempt! Would love to know what’s the proper way to do this!

// Requires an input in the form of mysterious(input)(arg1)(arg2)(arg3)...() as I'm not entirely sure how to terminate it otherwise
// This solution doesn't work if the original function performs any index-based operation on the arguments supplied, but I think that was long enough of a distraction :(

function mysterious(input) {
  function outer(firstArg) {
    let result = input(firstArg);
    
    function inner(arg) {
      if (arg) {
        result = input(result, arg);
        
        return inner; 
      }
      
      return result;
    }
    
    return inner;
  }
  
  return outer;
}

#7

Ok here are some hints:

  1. you want to know how many arguments original function takes
    if your function is add(a, b, c)
    you can get a number of arguments by storing add.length

  2. you want to return as many functions as there are arguments since every function will be taking one argument

  3. When you start executing those returned functions think what you want to do with the argument you passed


#8

Closure will do exactly want you want. Returning a function with previous parameters intact.

So you can write something like this.
Sum = Add(2)(3)(4);


#9

Closures are the mechanism that allow any of the solutions to work, but I think the challenge is for a function that will create a curried function dynamically.

This is unreliable as parameters with default values are not counted, and the count stops when a default value is reached.

function test(a, b="this is b", c) {
   console.log("test ", a, b, c);
}

test.length; // 1

I have an ES6-friendly solution:

function curry(fn) {
    const args = fn.toString().match(/function (?:\w*)\(([\w\,\s\=\"]*)\)/i)[1].split(',').length;
    const newArgs = [];

    return function getArg(x) {
        newArgs.push(x);
        if(newArgs.length === args) {
            fn(...newArgs)
        } else {
            return getArg;
        }
    }
}

This was a neat challenge. You should create an issue on the repo to see about getting it into the intermediate or advanced algorithms sections of the course.


#10

Can’t you just use “arguments” ?

arguments.length   // number of arguments passed to function
arguments[0]     // first argument 
arguments[n]     // nth argument 

#11

The arguments object is available to functions when they’re called.


#12

Currying is indeed a very interesting topic of functional programming more about it on wikipedia. @honmanyau for example asked what the “proper” way to do this would be.
My approach is more “proper” with respect to the examples in the other posts in the sense that it is a generalized form of @marinm’s mysterious() function compared to the previous solutions.

I am first going to assume that there is such a function (without explicitly defining it. That comes later) called curry() (the equivalent of the original poster’s mysterious() function) and that this function must satisfy a certain set of properties in order to work as desired.

Before we continue let us first define some things for later.

  • pure function:
    A pure function is a function that when called, introduces no side effects. That means the following.
    Given the same input the function will always return the same output regardless of the global program state.
    The global programm state at the point before calling the function will be the same after calling the function.
    This means that the function does not alter any global state.

  • arity:
    The number of arguments that a function f expects is called the arity of f.
    For example.

function add(a, b) {
  return a + b;
}

Here the function add() has an arity of 2.

  • fixed arity:
    The number of arguments that a function expects is fixed and not dynamic.
function foo(a) {
  if(arguments.length === 1) {
    return a * 2;
  }
  else if(arguments.length === 2) {
    return a * 2 + arguments[1] * 2
  }
}

Here foo() does not have a fixed arity. add() from the previous example does.

  • f(a)(b)(c):
    This is referred to as calling f in the curried form.

  • f(a, b, c):
    This is referred to as calling f in the list form.

Now let us state the properties that our mysterious function has to fullfill and then define such a function later on.
This approach is going to look rather rigorous (but don’t panic it’s easier than it looks)

Let F be an arbitrary pure function with fixed arity. Then there is another function let’s call it curry() that when applied on F returns a new function let’s call it f with f = curry(F) that satifies the following properties.

We can also apply that property partially (i.e not with all arguments at once) and a multiple number of times
For example it could look like one of these.




If F has arity n then f can be called p times with 0 < p <= n (either in list form or curried form) with k split points (k = p - 1) where a split point is (see example formulas above) when we change from list invocation to curried invocation or the other way around (from curried to list). We refer to each of those “times” (the p times) as a call interval i. The first interval is i1 then i2, i3, … up until ip the p-th call interval. Each interval can have an arbitrary length (i.e involves an arbitrary number of arguments) with the following constraints.

Further for any interval we have:

In this constraint k does not refer to the number of split points but just to any arbitrary interval that lies between 1 and p.

The following formulas give a full generalisation of the previous examples depending on wether you start out in the curried form or in the list form.

Calling in curried form first:

Calling in list form first:

So that’s it from a theoretical standpoint. The implementation is something else. A Javascript implementation might look similiar to this (spoiler! click to see).

function curry(fn) {
  var slice = Array.prototype.slice;
  var arity = fn.length;
  var args  = slice.call(arguments, 1);

  function acc() {
    var largs = args;

    if (arguments.length > 0) {
      largs = largs.concat(slice.call(arguments, 0));
    }

    if(largs.length >= arity) {
      return fn.apply(this, largs);
    }
    else {
      return curry.apply(this, [fn].concat(largs));
    }
  };

  return args.length >= arity ? acc() : acc;
};

There are some things one must be aware of when implementing this in javscript (works with fixed arities only!). We could also make a deep copy of the arguments instead of a shallow one. The following article explains it in much more detail. The implementation above is also more or less from it as after all that I was to tired to try my own solution. At last I made a small gist so you can see a small application/demo of our newly discovered function.

I put this post together after doodling around for the afternoon on these concepts. Let me know if you see any logical mistakes or errors in general so I can fix them.


#13

Thank you very much for taking the time to explain it with a more rigorous approach!

I decided to look at all of the code that has been posted so far because it’s definitely beyond me and it would take a few long sittings that I don’t quite have the luxury for right now to figure out a solution myself. :frowning:

Anyhow! I just have one small question with regard to the distinction you made between calling in curried from first and calling in list form first. If I haven’t missed something important, the code appears to be agnostic to which form is called first—were they were for illustration purposes or have I missed a link to to the relevant part in the code?

Thank you very much to everyone, it was a lot of fun and I learned a lot. Perhaps we could have an “Extra credits” forum category. d:


#14

I an really happy that this got some attention that was exactly the purpose.
I called it mysterious function because I wanted people to think about the problem without having any hints. I could have easily called my function curried or curry but that would spoil the thought process a little.
As someone above mentioned there are more ways to do this but it’s important to be aware of the possibility to execute function partially in different times.
And if One person has learned something new from this topic I consider it success.


#15

This was just the topic of today’s newsletter by Preethi Kasireddy.
She runs an email course called "Fundamentals of functional programming"


Free to signup.

On a related note, check out Ramdajs. It’s got a curry function.
http://ramdajs.com/docs/#curry
and other useful utilities/functions for working with sets (arrays, lists)


#16

Thanks @MarkoN95 for the in depth post! I look forward to reading it. Would be great to be able to properly understand this concept.

I solved something sort of related on codewars the other day but you can see it’s a little bit ugly and hardcoded for two params only.

function sum(x, y) {
   function addAnother (y) {
     return x + y
   }
   return (arguments.length < 2) ? addAnother : x + y
} 
sum(5,3) //8
sum(5)(3) // 8

Thanks @owel that looks like a great sign up. likely a bit advanced for me but still interesting.


#17

Your observation is correct. The code doesn’t care if you call it in the curried form first or in the list form. I just made these two distinctions because I couldn’t find a formula to generalize these two into one. So they basically express the same thing. I wanted to make sure that it’s clear that it doesn’t matter if you curry first or not. If you see a way to merge the two formulas into one that would be awesome!


#18

Here is my solution. I only tested it with a simple add function, but it should work for others.

function curry(fn, argc){
  var argc = argc || fn.length;
  function curried(x){
    return curry(fn.bind(null, x), --argc); 
  }
  if(argc <= 1){
    return fn;
  } else {
    return curried;
  }
}

#19

Ok, here’s my take. I guess it’s safe to say it works with a three-argument add function. But I can’t see how this can be generalized.

function add(a, b, c) {
  return a + b + c;
}

function mysterious(fn) {
  return function(a) {
    return function(b) {
      return function(c) {
        return a + b + c;
      }
    }
  }
}

var addOneAtATime = mysterious(add);
add(1, 2, 3) === addOneAtATime(1)(2)(3);

EDIT. I realized last night before going to sleep that I never used the fn parameter lol


#20

My first tought on any programming question is:
Is that problem already solved?
Several JS method (like forEach) take function with arguments as argument, so one possible solution can be found here.