Wednesday, November 13, 2013

Basic Currying Function in Dart


I continue to explore currying in Dart tonight. I have struggled with currying in Dart since a ways back. Actually, I should clarify that. I have struggled with a general purpose curry() in Dart.

It is quite easy to manually curry a function Dart. Consider the venerable add() function that simply adds three numbers:
add(x, y, z) {
  return x + y + z;
}
I can write a not-too-longhand curry of that as:
curriedAddManual(arg1) =>
  (arg2) =>
  (arg3) =>
  add(arg1, arg2, arg3);
The hash-rocket is Dart's syntactic sugar for return-what-i-am-pointing-at. So foo() { return 'foo'; } can be more succinctly written as foo() => 'foo';

So the curriedAddManual() takes a single argument and returns a function. The returned function takes a single argument and returns a function. Finally, that returned function takes a single arguments and returns the result of adding all three arguments. In other words, I have converted add(1, 1, 1) to curriedAddManual(1)(1)(1). That is the very definition of currying: converting a function that takes multiple arguments into a chain of functions that each take a single argument.

There are lots of good reasons to curry. There are many good articles on the JavaScript variety of currying that give reasons why one might want to curry, so I am not going to dwell on them too much. I will note that Dart's first class support of futures make curried functions quite attractive.

I believe that, after last night, I have the pieces necessary to write a curry function. At least the simple variety that can convert a function with an arbitrary number of arguments into a function that takes a single argument. The JavaScript version looks something like:
curry(fn) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    return fn.apply(
      this, 
      args.concat(
        Array.prototype.slice.call(arguments)
      )
    );
  };
};
This curry() function takes a function argument along with an arbitrary list of additional arguments. It returns a function that, when called with a single argument (i.e. when curried), will invoke the function with that single argument plus the list of arguments supplied to the curry() function.

Ah, the chicanery of Array.prototype.slice() in JavaScript. In the static-like world of Dart, the easiest way to get arguments that act like a list does not involve stuff like that, but it is not trivial. The easiest way to get access is via a noSuchMethod() definition in a class:
class Curry {
  noSuchMethod(args) {
    if (args.memberName != #send) return super.noSuchMethod(args);
    return _curry(args.positionalArguments);
  }
  // ...
}
The args object that is passed into noSuchMethod() is an Invocation object with useful properties like positionalArguments—the list of arguments supplied in order when the method was called. I do not strictly need to check that the method name (memberName) is send(), but I try to be a good citizen by checking for the #send symbol.

As for the _curry() method itself, it is fairly simple:
class Curry {
  noSuchMethod(args) {
    if (args.memberName != #send) return super.noSuchMethod(args);
    return _curry(args.positionalArguments);
  }

  _curry(args) {
    var fn = args[0],
        bound = args.sublist(1);

    return (_) {
      var _args = [_]..addAll(bound);
      return Function.apply(fn, _args);
    };
  }
}
The first parameter is the function being curried. The remaining arguments are the bound list of arguments. The _curry() method will return a function that takes a single argument (_). That single argument combined with the bound list become the entire list of arguments that will be supplied to the curried function when it is invoked.

With that, I can curry the add() method as:
var curried = new Curry().send(add, 1, 2);
curried(3) //  6
Going through a class like that is still somewhat roundabout. I will see if I can make that a little less opaque. I would also like to see if I can convert that to a true curry function—something that generates a chain of one argument functions. Tomorrow.


Day #934

3 comments:

  1. 1.) "That is the very definition of currying: converting a function that takes multiple arguments into a chain of functions that each take a single argument."

    Right. But the currying is the conversion, and not the result. And therefore your first example (curriedAddManual) may be a bit misleading. A first approach could be to introduce a first automatic currying step, where only the number of parameters is fixed:

    curry3(f) => (arg1) => (arg2) => (arg3) => f(arg1, arg2, arg3);

    var curriedAdd = curry3(add);
    curriedAdd(1)(1)(1);

    This would convert any 3-parameter functions into its curried form. The only last task is to abstract over the amount of parameters.

    2.) Your second example ( _curry(args)) is indeed NOT currying but partial function application: You take a multi param function and immediately apply all but one parameters ( send(add,1,2) ), resulting in a function that takes the remaining one immediately giving the end result.
    I.e.: the following is not possible:

    var curriedAdd = curry3(add);

    var takenOne = curriedAdd(1);
    var takenTwo = takenOne(2);
    var takenThree = takenTwo(3);
    print (takenThree); // --> 6

    So I'm looking forward for what you will come up with.

    3.) It is really sad that such a recent and modern programming language like Dart is not more designed around functional programming, so that such typical things like currying need to be solved in such cumbersome ways.

    ReplyDelete
    Replies
    1. 1.) Thanks for the correction. I tend to get a little loose with my terms and will try to pay more attention.

      2.) Agreed. I had hoped to get actual currying working in the next post (http://japhr.blogspot.com/2013/11/warning-possible-call-abuse-in-dart.html), but got side-tracked with other fun stuff. Tomorrow for sure!

      3.) I wouldn't call it sad. In my experience, the areas that the language designers chose to focus for 1.0 have proven highly satisfactory in my experience. Even though FP can be rough, it's still possible (hopefully!). That said, I would not mind in the least if this gets easier in the future :D

      Delete
  2. Japh(R) By Chris Strom: Basic Currying Function In Dart >>>>> Download Now

    >>>>> Download Full

    Japh(R) By Chris Strom: Basic Currying Function In Dart >>>>> Download LINK

    >>>>> Download Now

    Japh(R) By Chris Strom: Basic Currying Function In Dart >>>>> Download Full

    >>>>> Download LINK nX

    ReplyDelete