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); // 6But 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); // 6Yay!
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