by bvisness on 6/22/23, 4:35 AM with 122 comments
by jcarrano on 6/22/23, 9:34 AM
I'm annoyed when coroutines are reserved for use only in high performance, c10k-type of situations. For example, the KJ library's doc says:
"Because of this, fibers should not be used just to make code look nice (C++20's co_await, described below, is a better way to do that)."
With this "stackless or nothing" attitude we do not have good C++ coroutine libraries outside C++20's.
For me the purpose IS to make the code look nice and I do not care if the coros are stackfull and consume more stack memory. At the end of the day, for my application, I saved more in programmer's time and bugs than I lost in RAM (and I'm on an embedded board with only 64MB)
by taeric on 6/22/23, 2:40 PM
Seeing them make an odd resurgence in recent years has been awkward. I'm not entirely clear that they make things much more readable than alternatives. Reminds me of thinking continuations were amazing, when I saw some demos. Than I saw some attempts at using them in anger, and that rarely worked out that well.
Also to the point of the article, I love being "that guy" that points out that LISP having a very easy "code as data" path makes the concerns expressed over the "command" system basically go away. You can keep the code as, essentially:
(DriveForward 0.5 48)
(while (NotCarryingBall)
(Grab)
(pause 2))
(DriveBackward -0.5 48)
(Shoot)
With god knows how much bike shedding around how you want to write the loop there.Of course, you could go further for the "pretty" code that you want by using conditions/restarts such that you could have:
(DriveForward 0.5 48)
(Grab)
(DriveBackward -0.5 48)
(Shoot)
And then show what happens if "Grab" is unsuccessful and define a restart that is basically "sleep, then try again." Could start plugging in new restart ideas such as "turn a little, then try again." All without changing that core loop.by samsquire on 6/22/23, 8:48 AM
I don't like hardcoding functions in coroutine pipelines. Depending on the ordering of your pipeline, you might have to create things and then refer to them, because of the forward reference problem.
Here's my stackoverflow question for what I'm getting at:
https://stackoverflow.com/questions/74420108/whats-the-canon...
Coordinating work between independent threads of execution is adhoc and not really well developed. I would like to build a rich "process api" that can fork, merge, pause, yield, yield until, drop while, synchronize, wait (latch), react according to events. I feel every distributed systems builds this again and again.
Go's and Occam's CSP is pretty powerful.
I've noticed that people build turing completeness ontop of existing languages, probably due to the lack of expressivity of the original programming langauge to take turingness as an input.
by quantified on 6/22/23, 7:03 AM
Java's not the easiest to pick up in high school unless you really make a big after-school effort. Something like Lua is probably better.
by rtpg on 6/22/23, 8:59 AM
The biggest challenge is sometimes you do want external flow control, and there coroutines can get hard to untangle if your design is a bit messy.
Something like “A signals to B to do something else” starts to be a bit tricky (along with interruptible actions). I think there are good patterns in theory but I’ve found myself with pretty tangled knots at times.
This is ultimately a general problem when programming everything as functions. Sometimes you need to mess with state that’s “hidden away” in your closure. Building out control flow data structures ends up becoming mandatory in many cases.
by pcblues on 6/22/23, 5:46 PM
My teacher (Hi Mr Steele if you are still kicking around!!) taught us the algorithms without coding, but instead used playing cards or underwater bubbles or whatever. We had our a-ha moments intellectually before we implemented them in code.
As an aside, our school had just got macs and we spent most of the day playing digitised sound files from Monty Python.
"YOU TIT!"
by mgaunard on 6/22/23, 9:11 AM
They're equivalent except that asynchronous callbacks is what actually happens and you have clear control and visibility on how control flow moves.
by Izkata on 6/22/23, 12:09 PM
Every tick, inspect the state of the world. Then do the one thing that gets you a single step towards your goal.
At first I wasn't sure the "inspect" part was possible in the robot system, but the Lua code makes it look like it is? If so, the change is basically changing the "while" to "if" and adding additional conditions, maybe with early returns so you don't need a huge stack of conditions.
The "converging" style doesn't use coroutines and is more robust. Let's say, for example, another robot bumps into yours during the grab - the Lua code couldn't adapt, but the "converging" style has that built in since there's no assumed state that can get un-synchronized with the world like with a state machine / coroutine version. It was because of external interactions like that, that I couldn't 100% rely on but were inspectable, that I originally came up with this style.
by taberiand on 6/22/23, 8:24 AM
IEnumerable<RobotCommand> MyRobotBrain()
{
while(drivetrain.getDistanceInches() > -48)
{
yield drivetrain.arcadeDrive(0.5, 0);
}
yield shooter.shoot();
}
by vanderZwan on 6/22/23, 12:36 PM
// an external input event channel
input int KEY;
// par/or concurrently executes two
// or more blocks (called "trails")
// if one of them terminates, the
// other trails are aborted.
// Compare: par/and, which waits
// for all trails to terminate
// before resuming code
par/or do
// an infinite loop that awaits
// a timer event. Therefore this
// trail never terminates by itself
every 1s do
// Ceu uses C as a host language
// and compiles to (essentially)
// a giant finite state machine
// C functions can be accessed
// using an underscore prefix
_printf("Hello World!\n");
end
with
// this trail awaits a keypress,
// then terminates, ending the entire
// par/or block.
await KEY; // awaits KEY input event
end
_printf("Bye!\n");
The above code prints "Hello World!" ever second, until a key is pressed, after which it prints "Bye!" before terminating the program.The reactivity and intuitive single-threaded concurrency makes it a really nice language for low-powered devices. Or robotics, which is a lot about reacting to sensor input.
It has a dedicated Arduino repo too[1].
In practice it's more of a research language by Francisco Sant’Anna, a professor at UERJ, Brazil, than a language with a big community around it. He's currently working on a new version caleld Dynamic Ceu, or dceu[2]
In the same paradigm there is the Blech[3] language, I believe originating from Bosch. Sadly, that project also has lost some steam.
[0] https://github.com/ceu-lang/ceu-arduino
by gavinray on 6/23/23, 3:25 AM
Your desired pseudo-code in Java would look like this in Kotlin:
coroutineScope {
// Drive backward
launch {
while (drivetrain.getDistanceInches() > -48) {
drivetrain.arcadeDrive(-0.5, 0)
}
drivetrain.arcadeDrive(0, 0)
}
// Grab for two seconds
launch {
val grabTimer = Timer()
while (grabTimer.get() < 2) {
intake.grab()
}
intake.stopGrabbing()
}
// Drive forward
launch {
while (drivetrain.getDistanceInches() < 0) {
drivetrain.arcadeDrive(0.5, 0)
}
drivetrain.arcadeDrive(0, 0)
}
}
by ifyalciner on 6/22/23, 9:53 PM
Shameless plug, I have also been developing (weren't planning to advertise yet so no docs or plans for release) a behaviour based C++ library completely built on coroutines [4] to avoid some problems of "Behaviour Tree"s.
[1] https://en.wikipedia.org/wiki/Behavior_tree_(artificial_inte... [2] https://en.wikipedia.org/wiki/Behavior_tree_(artificial_inte... [3] https://github.com/BehaviorTree/BehaviorTree.CPP [4] https://gitlab.com/ifyalciner/ferguson
by alex-moon on 6/22/23, 7:37 AM
Funnily enough I have used the command pattern before to automate sequences of steps in a workflow. Back then I kind of stumbled on it - it is super effective for certain kinds of things.
by dools on 6/22/23, 10:39 AM
All the details about the actual commands to complete the objective and checking the state and so on go into the classes.
If no more objectives in the list, mission accomplished.
You can also easily test each objective independently.
Maybe the trouble is trying to fight abstraction so hard in the first place.
by brooke2k on 6/22/23, 10:42 PM
Since a game runs as a loop, all synchronous code needs to be able to execute within one frame.
So you end up making overcomplicated state machines to represent processes that last for multiple frames.
Coroutines make writing this code sooooo much easier. You can actually start to see the logic again at a glance rather than having to dive into a big stateful mess.
by jezzamon on 6/22/23, 2:37 PM
When I create games, there's also a similar tick() function that's called every frame to handle the main logic, and normally to handle logic that needs to execute over multiple frames I write state machines like the article talks about, coupled with promises to allow things to chain off each other. This article might change how I write that code??
I'm also thinking that instead of using a coroutine and yield(), you could asynchronous functions and "await nextTick()". Mostly for my own sake, trying to think of the differences between the two between the two:
- Coroutines allow the decision of when to execute them to be made outside the function. Whereas asynchronous code goes off and does its own thing. With the corountine approach you fit into the normal code flow of having a tick function that does something every frame, so that seems better.
- This approach only supports one coroutine at a time, whereas it's easy to kick off parallel operations with async code. Not being able to do things in parallel is probably desirable for a robot like this, as otherwise you might accidentally try to move towards two different goals at the same time.
- You can interrupt a coroutine by just not calling it anymore. Async code generally can't be interrupted by an external function. So it would be easier to switch to doing new behaviour.
- Composability. A quick search seems to indicate that support for nested coroutines in lua is not very good [1]. Which means that you can't split your main logic into small functions. In my language of JavaScript though, this isn't a problem thanks to yield*. But seems like asynchronous functions might win out there.
Hm. More to think about. I suppose I'll try it on my next game jam.
[1]: Is this the only way to yield all the results of a second coroutine in Lua?? https://gist.github.com/nicloay/2b893b3de1d964dcc92023c6318a...
by erdaniels on 6/22/23, 1:14 PM
by shadowgovt on 6/22/23, 12:09 PM
At least the pedagogy is accurate. From UIs to database accessors, coding modern Java basically is creating a crappy programming language out of Java classes.
by ptr on 6/22/23, 9:45 AM
by calf on 6/22/23, 11:21 AM
Language support may be the deciding factor for young students, but conceptually the more interesting engineering debate would be which paradigm is better for a given purpose, coroutines or commands/transactions assuming the programming language can cleanly express both.
by foxbyte on 6/22/23, 6:53 PM
by baylessj on 6/22/23, 4:19 PM
by enum on 6/22/23, 12:05 PM
by mike_hearn on 6/22/23, 5:34 PM
by nstbayless on 6/22/23, 6:02 PM
To the author: you could make the code even cleaner by moving the yield to within the action functions. Though maybe this won't work as well for parallel actions...
by actinium226 on 6/22/23, 2:38 PM
by Dudester230602 on 6/22/23, 8:31 AM
by pas on 6/22/23, 8:36 AM
yes, new Java(new Java(1), new ...) is bad, and asynchronous programming paradigms are usually fitting for robotics, but abstractions are good.
by giovannibonetti on 6/22/23, 10:46 PM
by scotty79 on 6/22/23, 12:29 PM
I'd really love to see async/parallel language based on these ideas.
by TheAlchemist on 6/22/23, 12:12 PM
I'm currently watching some videos by David Beazley, and he does several talks on coroutines in Python - very much recommended.
by mkoubaa on 6/22/23, 11:57 AM
by cryptonector on 6/22/23, 2:34 PM
by noobermin on 6/22/23, 8:56 AM
by greatgib on 6/22/23, 6:39 AM
by sesuximo on 6/22/23, 1:10 PM