Friday, November 15, 2013

Real, Live Currying of Dart Functions


I am nearly done with my general-purpose curry() function in Dart.

I have a class-based solution which enables something akin to actual currying:
class Curry {
  static final Curry currier = new Curry._internal();
  static get make => currier;
  Curry._internal();
  noSuchMethod(args) {
    if (args.memberName == #send || args.memberName == #call) {
      return _curry(args.positionalArguments);
    }
    return super.noSuchMethod(args);
  }

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

    return (_) {
      var _args = [_]..addAll(bound);
      return Function.apply(fn, _args);
    };
  }
}
The first half of the Curry class creates a singleton instance of Curry and makes it available as either Curry.currier or Curry.make. The noSuchMethod() is a Dart's method lookup of last resort. If the method is nowhere else to be found, Dart will call this method, if defined, with an aruguments-like object (of Invocation type). In there, I handle either send() or call(). In either case, it is at that point that the actual currying takes places. The call() method has the side-benefit of allowing me to treat instances of Curry like functions: Curry.make(add, 1, 2).

All this works brilliantly if I curry the right number of arguments. If the add() function takes 3 parameters, I have to manually supply two of the arguments so that _curry() returns a function that expects the last variable to be supplied:
add(x, y, z) {
  return x + y + z;
}
var curried = Curry.make(add, 1, 2);
curried(3); // 6
But that is not real currying. It's more like broken partial application of functions. Currying involves rewiring multi-parameter functions as a chain of single parameter functions. I ought to be able to supply one bound argument to Curry.make() to produce a function that takes a single argument, which then chains the next single argument:
var curried_1 = Curry.make(add, 1);
curried_1(2)(3);
If curried_1() was properly curried, then I the above should produce 6. But instead I get:
NoSuchMethodError: incorrect number of arguments passed to method named 'call'
  Receiver: Closure: (dynamic, dynamic, dynamic) => dynamic from Function 'add': static.
  Tried calling: call(2, 1)
  Found: call(x, y, z)
This is caused by the _curry() method. I supplied one bound variable when I made curried_1(). When I called the returned function with a single argument, the number 2, the function adds all of the bound arguments (1) to the list of new arguments (2) and tries to call the function. Since I only have 2 arguments, calling the a 3 parameter function rightly results in an error.

What I need to do is not return the function call until I have all of the parameters. If the list of bound variables plus the new argument is less that the total number of arguments in the function being curried, then I need return the result of one more curry. For this, I am going to need mirrors:
import 'dart:mirrors';
class Curry {
  // ...
}
Mirrors allow me to reflect on the structure of my running code and to dynamically evaluate things. I will need both. First, I need to know how many parameters the function being curried expects. For that, I reflect on the function, and ask the mirror returned for function property's parameter size:
import 'dart:mirrors';
class Curry {
  // ...
  _curry(args) {
    var fn = args[0];
    var bound = args.sublist(1);
    var size = reflect(fn).function.parameters.length;
    // ...
  }
}
Easy enough.

Next, if the number of arguments is less than the number of parameters in the function, I need to curry the function again. For that, I need an InstanceMirror of the current object:
import 'dart:mirrors';
class Curry {
  // ...
  _curry(args) {
    var fn = args[0];
    var bound = args.sublist(1);
    var size = reflect(fn).function.parameters.length;

    var im = reflect(this);
    // ...
  }
}
That instance mirror will let me invoke a method with a list of parameters. I do this when there are still not enough bound arguments:
import 'dart:mirrors';
class Curry {
  // ...
  _curry(args) {
    var fn = args[0];
    var bound = args.sublist(1);
    var size = reflect(fn).function.parameters.length;

    var im = reflect(this);
    return (_) {
      var _args = [_]..addAll(bound);

      if (_args.length < size)
        return im.invoke(#send, [fn]..addAll(_args)).reflectee;

      return Function.apply(fn, _args);
    };
  }
}
Since invoke() returns another InstanceMirror, I actually end up returning the relfectee property. And that turns out to be the last little trick necessary.

Because now I can curry the add() function with a single parameter and then get the actual result by chaining single parameter function calls:
var curried_1 = Curry.make(add, 1);
curried_1(2)(3); // 6
Yay!

As I noted yesterday, it is possible to do this in a simpler fashion if I were willing to use functions and a list of optional parameters. For demonstrating Dart's functional simplicity, that would have been the way to go. But I am thrilled to have worked out this general-purpose solution. This was some fun functional programming!


Day #936

No comments:

Post a Comment