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