Thursday, November 14, 2013

WARNING: Possible call() abuse in Dart


Thanks to some noSuchMethod() Dart magic, I have a basic currying function. Well, since it is no such method, I have a basic currying class. I hope to improve that class's capabilities and interface tonight.

There is no way (that I know) to get arbitrary function parameters in Dart. I could use optional positioned parameters:
curry([a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]) {
  // curry here...
}
In reality, there is never going to be a need to curry a function with more than 10 parameters, so that would be a reasonable approach. But c'mon. That just looks silly.

With my class based approach, I have to create a Curry instance, but once I have that, I can curry any function I like, regardless of how many parameters is has:
var curried = new Curry().send(add, 1, 2);
curried(3) // 6
The first line creates a curried version of the venerable add-three-numbers-together function that is called with three parameters: add(1, 2, 3). Currying means converting a function with multiple parameters into a chain of single argument functions. This what the Curry instance above does. When the add() function and two parameters are sent to the currier, a function that takes a single argument is returned.

The Curry class that I came up with last night looks like:
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);
    };
  }
}
As mentioned, the noSuchMethod() method is special in Dart. If defined, it will be passed an Invocation object that contains meta information about a method that was called, but not explicitly defined. It is a nice meta-programming facility built into Dart.

In the above, if the method name is not send (checked using Dart's Symbol notation), then I return the superclass's noSuchMethod(), which will throw an error. If the method was send(), then I pass the args Invocation object to the _curry() method to do some currying.

The first thing that I would like to do tonight is convert that into a singleton object. Sure, it is a superficial change, but there is never a reason to have multiple instances of a Curry class. Singletons are easy in Dart. I create a named, private constructor as the only way to create an instance. Then a factory constructor ensures that the same instance, created once, will always be used:
class Curry {
  static final Curry currier = new Curry._internal();
  static get make => currier;
  factory Curry() {
    return currier;
  }
  Curry._internal();
  // ...
}
I have also defined a make alias for the singleton instance of Curry. This will allow me to invoke the currier as Curry.make. I am also declaring it without a type, which will be important in a bit.

Even with all of this, my usage has not improved dramatically:
var curried = Curry.make.send(add, 1, 2);
curried(3); // 6
What I would really like is to drop the send entirely:
var curried = Curry.make(add, 1, 2);
curried(3); // 6
But that won't work, right? There is no way to treat an object (make is an object of type Curry) as a function. Or is there? In Dart, there is. To do just that, the class needs to define a call() method. Or in this case, handle a call() inside of noSuchMethod():
class Curry {
  static final Curry currier = new Curry._internal();
  static get make => currier;
  factory Curry() {
    return currier;
  }
  Curry._internal();

  noSuchMethod(args) {
    if (args.memberName == #send || args.memberName == #call) {
      return _curry(args.positionalArguments);
    }

    return super.noSuchMethod(args);
  }
  _curry(args) { /* ... */ }
}
And that actually works. I had expected the need to actually define a call() method, but it turns out to be unnecessary. Without an explicitly defined call() Dart invokes noSuchMethod() with the same Invocation object that it does when send() was called on the instance. Only since the object is being called, this works:
var curried = Curry.make(add, 1, 2);
curried(3); // 6
It is entirely possible that I am getting away with something here, but the best part of all of this is that dartanalyzer does not complain when it performs static type analysis. It does not complain because I did not declare a type on make. But hey, the code works.

For what it is worth, if I try to use the currier static instance variable, which is declared with a type, then dartanalyzer does complain. It warns that there is no call() method defined in the Curry class:
[warning] 'currier' is not a method (/home/chris/repos/csdart/Book/code/functional_programming/first_order.dart, line 102, col 27)
If I declare an empty call():
class Curry {
  // ...
  call(){}
  // ...
}
Then dartanalyer complains about the arity:
[warning] 0 positional arguments expected, but 3 found (/home/chris/repos/csdart/Book/code/functional_programming/first_order.dart, line 103, col 34)
So, in the end, the non-typed static alias seems a perfectly valid workaround since (a) I love static type analysis and (b) don't want to be warned when I am playing fast and loose with it.

There is still one more improvement that I would like to make to my currying function before moving on to other topics. If my 3 argument function is curried with only a single argument, then invoking the curry should return a partially applied function. I am reasonably sure that I can make that work, but I will wait until tomorrow to verify.



Day #935

No comments:

Post a Comment