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) // 6The 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); // 6What I would really like is to drop the send entirely:
var curried = Curry.make(add, 1, 2); curried(3); // 6But 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); // 6It 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