While performing some minor refactoring on Design Patterns in Dart code (public repo), I ran across an interesting problem. What is the cleanest way to define a recursive function and immediately call it?
I am nearing the end of preliminary research for the book so I am cleaning code up as much as possible for future me. One of the snippets that I lingered on was the loop in the Reactor Pattern code:
// Reactor “loop” (handleEvent is recursive)
new InitiationDispatcher().handleEvents();
I can't just make this into a for
loop because the loop would always run in the current event frame—never allowing other code to send data into the loop nor allowing the event loop to perform asynchronous actions. So a recursive function seemed the next best idea. Each new call to the function would place it next on Dart's event loop, first allowing any other tasks on the queue to execute.It seems perfect… except from a clean code standpoint. Why include the comment about the recursive function/method when I can put the actual code right there? As a bonus, this would better follow the code from the original Reactor Pattern paper.
This leads to the question that I started with: how do I define a recursive function in Dart and immediately call it? In JavaScript, the following will work:
(function foo() { setTimeout(foo, 1000); console.log('foo!'); })()The outer parentheses encapsulate a function, which I immediately invoke with the call operator at the end of that line—the open/close parentheses. Inside the parentheses, I declare the function
foo()
and invoke it inside a setTimeout
. Strictly speaking the setTimeout()
is not necessary, but without it I would consume a heck of a lot of my computer's CPU to print out the word “foo” at the end of the function.But if I try the same thing in Dart:
import 'dart:async'; final timeout = const Duration(milliseconds: 10); main() { ( foo(){ new Timer(timeout, foo); print('foo!'); } )(); }Then I get an invalid syntax error:
'file:///home/chris/repos/design-patterns-in-dart/reactor/bin/reactor.dart': error: line 6 pos 10: ')' expected foo(){ ^It seems that Dart does not consider a function declaration an object that can be grouped by parenthesis.
Similarly, it does not work to simply place the parentheses after the closing curly brace:
foo(){
new Timer(timeout, foo);
print('foo!');
}();
This results in another syntax error:'file:///home/chris/repos/design-patterns-in-dart/reactor/bin/reactor.dart': error: line 8 pos 5: unexpected token ')' }(); ^What is interesting about the error with this approach is that it will work if the function is anonymous:
(){
new Timer(timeout, foo);
print('foo!');
}();
Well, “work” might not be the right term here. Not fail at the same place would be more apt. This does call the anonymous function right away, but I have not assigned foo
, so I wind up with an error trying to invoke it in the Timer:./bin/reactor.dart foo! Unhandled exception: The null object does not have a method 'call'. NoSuchMethodError: method not found: 'call' Receiver: null Arguments: [] #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:45) #1 _createTimer.<<anonymous closure> (dart:async-patch/timer_patch.dart:9)If I try to assign a variable
foo
to the anonymous function and then call it right away: var foo=(){
new Timer(timeout, foo);
print('foo!');
}();
Gives me yet another error. This time, I cannot use a variable in the function definition when I am declaring that same variable:'file:///home/chris/repos/design-patterns-in-dart/reactor/bin/reactor.dart': error: line 8 pos 7: initializer of 'foo' may not refer to itself var foo=(){ ^So, despite my efforts, the best solution that I can devise is to declare the variable elsewhere:
var foo; final timeout = const Duration(milliseconds: 10); main() { (foo=(){ new Timer(timeout, foo); print('foo!'); })(); }Which is pretty ugly. It is so ugly that I might just stick with a plain old function declaration coupled with a separate call:
foo(){
new Timer(timeout, foo);
print('foo!');
}
foo();
In just about all cases, I prefer Dart syntax to JavaScript. But I think I finally found one in which JavaScript has Dart beat.Day #138
I haven't a solution to the recursive problem you've encountered, but how about doing this instead.
ReplyDeletenew Timer.periodic(timeout, foo);
void foo(Timer t) {
print('foo');
}
That might do the trick?
Yup, that'll also work in this case. But I still want to be able to do this kind of thing. This is not the first time that I've run into this problem in Dart — just the first time I whined about it publicly. Well, whined and tried to systematically work through the various approaches that I could think to try...
DeleteIt's a good thing nobody claimed Dart was prefect.
DeleteDeclare the local variable ahead of time:
ReplyDeletevar foo;
foo = () { ... };
foo();
That's effectively what I wound up doing (https://github.com/eee-c/design-patterns-in-dart/blob/master/reactor/bin/reactor.dart#L17-L22). But that's 3 lines of Dart to 1 line of JavaScript. It's usually the other way around :-\
Delete