24 ms·
Coroutines make robot code easy
- ifyalciner 3y agoCoroutines (or the concept of saving context to come back to) is already heavily explored in robotics in recent years. Behaviour Trees [1] approximates what coroutines do in system level controlled ticks mainly to avoid pitfalls of state machines. They are also extensively used in game development too. ROS2 have Nav2[2] package which is based on BTCpp library [3]. Not surprisingly BTCpp library uses boost coroutines to implement some behaviours. 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_intelligence,_robotics_and_control) https://en.wikipedia.org/wiki/Behavior_tree_(artificial_inte... [2] https://en.wikipedia.org/wiki/Behavior_tree_(artificial_intelligence,_robotics_and_control) https://en.wikipedia.org/wiki/Behavior_tree_(artificial_inte... [3] https://github.com/BehaviorTree/BehaviorTree.CPP https://github.com/BehaviorTree/BehaviorTree.CPP [4] https://gitlab.com/ifyalciner/ferguson https://gitlab.com/ifyalciner/ferguson
- josephg 3y agoGiven your expertise, why use behaviour trees when coroutines are available? What value do they provide in languages like javascript where generators work well?
- ifyalciner 3y agoBehaviour Trees are a bit more than that. They also effect/advice how you structure your project (not component based but behaviour based: exp. you don't have "controller" component in your architecture to handle all locomation but "go_to_pose" or "turn_around" behaviour) plus there are tooling provided around those architectures. Design of Behaviour Trees were not an answer to lack of coroutines/generators (although they were missing from C++ standards until C++20) but an alternative to state machines. Although I agree that "tick" based BT implementation is outdated and should completely be replaced by coroutines and mandatory yields (thats what I did in my implementation). There is still value in behaviour based architectures in robotics. They make building complex/reactive behaviours much easier than component based ones.
- jeffomatic 3y agoI'm writing a game that uses both behavior trees and coroutines (via C# generators), and I have found that they are not quite interchangeable. The main thing is that the execution model is different. I think of the behavior tree as being evaluated from the root on every tick, whereas with coroutines, you are moving linearly through a sequence of steps, with interruptions. When you resume a coroutine, it picks up exactly where it left off. The program does not attempt to re-evaluate any preceding code in the coroutine in order to determine whether it's still the right thing to be executing. By contrast, a behavior tree will stop in the middle of a task if some logic higher up in the tree decides that you need to be on a different branch. What we end up doing is composing behavior trees with coroutines as leaf nodes. It works quite nicely, although I wish there was a way to express the structure of the behavior tree in a more elegant way. We do the obvious thing: each node in the tree is some subclass of a Node base class, representing a logical operation like "if" or "do these in parallel". I very much feel OP's angst about creating a pseudo-programming language-within-a-language by creating what are basically ad hoc AST nodes, but I haven't come up with a better solution. Maybe something like React, where you use basically imperative code to describe a structure, and there is some ambient state that gets properly reconciled by a runtime that you don't touch directly.
- brooke2k 3y agoI had a similar revelation a few months ago when working on a little game in Lua. 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.
- giovannibonetti 3y agoRelated discussion: "Notes on structured concurrency, or: Go statement considered harmful" https://news.ycombinator.com/item?id=16921761 https://news.ycombinator.com/item?id=16921761
- gavinray 3y agoYou could have used Kotlin if you wanted to stay on the JVM and use coroutines. 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) } }
- greatgib 3y agoIf you want to make it easy for high schoolers, just don't use java in the first place...
- franga2000 3y agoHaving learned Java in high school and later taught Java, Python and C++ to high-schoolers, things have improved greatly in Java. The days of memorizing the entire BufferedReader try-catch snippet are over. The new file and stream APIs are almost as clean as in Python and making GUIs (Swing for basic and JavaFX for advanced) is even easier than Tk in Python. For me, it boils down to a choice between typed and "non-typed" languages, where the best typed option is Java and the best "non-typed" are Python or maybe JavaScript (NodeJS). Everything else is some combination of not mainstream enough, not powerful enough out of the box or requires too much knowledge to get started. Java and Python read like English, have most of what you'll need included and are very useful languages for students to get their first internships/summer jobs working with.
- rootlocus 3y agoWe learned borland pascal and borland c in high school. Java is perfectly fine.
- 62951413 3y agoDon't get me wrong, I miss Turbo Vision-based UIs and an IDE that fits onto a single floppy disk. But the JVM is a professional tool to begin with. So if you cannot start with a toy such as golang or python at least go with Kotlin. Borland Pascal already had reasonable static types, classes, pointers, modules with a public API, and blinding compilation speed in the early 90s. All the basic building blocks to grok. Golang seems to be the closest modern-day approximation.
- bedobi 3y agoI went to a university where Java was the main language for most of the basic programming courses I've been making a living coding in Java for 10+ years I'm a strong believer in types etc etc I still don't think Java is a great language to teach programming lol too much pointless boilerplate and abstraction to achieve the simplest things lots of footguns and objectively bad standard practices built into the language and the native libraries to me Python and Kotlin seem like probably better choices (though they too have massive flaws)
- quantified 3y agoThankfully a piece that emphasizes that coroutines are functions that pause. Java frameworks like Quasar became focused on other goals besides that basic capability and lost their way (IMHO). 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.
- happymellon 3y agoI don't know Quasar, but a lot of projects seem to want to add functionality rather than be a library for that purpose, and create a new one for the completely unrelated feature that you want.
- moffkalast 3y agoUnfortunately high schools are forced to continue teaching Java, or else Oracle starts executing hostages.
- galangalalgol 3y agoIn my day Borland had all the hostages, and every time you used something that wasn't orange text turbo pascal another one got fed to a gru. The right intro language is an interesting study in itself and the intuitiveness of coroutines is an good data point. Go seems like a decent first one. It jas dark corners, but at least they are in the corner. I'd love to argue for rust as a first language, and maybe it isn't a bad one. But I'm not sure where I'd start the argument. C was my second language, and I'm not sure it would have made sense as qyuckly before commodore basic.
- gorjusborg 3y agoI feel bad for autonomous participants, Java is almost a uniquely poor choice for the size of project FIRST teams would be creating. Java has its domains, but small, algorithm driven code worked on by a small, inexperienced group isn't one.
- bvisness 3y ago
- alex-moon 3y agoHad a go at writing a game a while back, purely for fun, reinventing the wheel to learn about what makes writing games hard. The command pattern here is a really great way to solve a persistent difficulty I had (largely the same one discussed in the article). I have certainly missed the point of the article but that is a great takeaway for me personally. 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.
- taberiand 3y agoSimilar code could be written in C#, with code like IEnumerable<RobotCommand> MyRobotBrain() { while(drivetrain.getDistanceInches() > -48) { yield drivetrain.arcadeDrive(0.5, 0); } yield shooter.shoot(); }
- deleted 3y ago[deleted]
- isanjay 3y agoDots instead of colons ?
- taberiand 3y agoYes good catch; oversight from posting on mobile. I've fixed it up
- sparkie 3y agoI seem to recall reading (though quite some time ago so I may be mistaken), that `yield` was developed for use in robotics as part of the CCR (Concurrency and Coordination Runtime), and it's inclusion in C# 2.0 was to support this.
- thaumasiotes 3y agoI read the article, but I failed to understand the problem. What's happening when you pause? Why do you need your functions to pause? The example robot involves taking a deterministic sequence of actions in a fixed order. The state machine is a line. The article seems to be pretty clear that this code is bad: while(drivetrain.getDistanceInches() > -48) { drivetrain.arcadeDrive(0.5, 0); } and this code is good: while(drivetrain.getDistanceInches() > -48) { yield drivetrain.arcadeDrive(0.5, 0); } But pausing doesn't seem to be the functionality that's missing. The first loop will drive backwards until getDistanceInches is at most -48. The second one will also drive backwards until getDistanceInches is at most -48, but it will "pause" intermittently while it drives there. If my robot drives all the way in, I guess, one step (?), what will go wrong? The tick function is called 50 times per second. Is it constrained to terminate within 0.02 real-time seconds? What if you want to think hard about something?
- Dudester230602 3y agoSo many similarities with game development: central loop, coroutines, character state machine etc.
- ralphc 3y agoAn autonomous robot is just a NPC in the real world.
- pas 3y agoleaky abstractions and all aside, the coroutine code is unreadable to me after clean looking commands. :| yes, new Java(new Java(1), new ...) is bad, and asynchronous programming paradigms are usually fitting for robotics, but abstractions are good.
- ptr 3y agoCoroutines are an abstraction though.
- samsquire 3y agoI think that developers have minimal scheduling primitives available to them, to schedule complicated work, in the order and timings you want it to have. 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-canonical-approach-to-creating-multiple-producers-and-consumers-with 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.
- noobermin 3y agoI mean, this is an interesting thing to think about but isn't at all what the article is about...coroutines have many uses may be and are often related to async work in general but OP is using coroutines just as a pauseable function (in good old asm, you know how computers actually just work, this is just jumping into a subroutine).
- samsquire 3y agoI think the article implements stackful coroutines since the yield calls a function where control flow jumps to, it eventually returns to the instruction(s) after the yield statement. (That is, it's not a JMP)
- xigency 3y agoI’d recommend exploring some with Scheme. Somewhere between writing code in continuation-passing style, using macros, and using call-with-current-continuation it should be able to build any of these mechanisms in a clean way. Maybe there’s a prototype for a better construct there. Then it’s on other language developers to support these capabilities as well. Because looking at all the examples listed in the article, none of them seem like very idioms.
- deleted 3y ago[deleted]
- noobermin 3y agoAh coroutines, essentially glorified goto. My 10keV take is that dijsktra's paper while probably right about goto back then has cursed us and we are still cursed till this day. Some logical structures map well to just using goto in a series of steps, but because of this allergy to goto, we're stuck where basically jumping into a random place in a function is "novel."
- kachnuv_ocasek 3y agoCoroutines/continuations are not “basically jumping into a random place in a function”. It's pausing the execution of a function in a well-defined place with well-defined, understandable semantics for resuming the execution.
- rtpg 3y agoCoroutined make programming for pico8 very chill as well. 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.
- mgaunard 3y agoPeople always say that coroutines make code easier to understand, but I've always found normal asynchronous code with callbacks much easier to understand. They're equivalent except that asynchronous callbacks is what actually happens and you have clear control and visibility on how control flow moves.
- armitron 3y agoI imagine you would get a lot of blank stares with that POV, at least from folks with working bullshit detectors (like young kids that haven’t been conditioned to “modern” industry practices). I found the article a great example of the kind of crap that passes for programming these days.
- shadowgovt 3y agoIn the context of the first robotics competition, teaching the command hierarchy is a good opportunity to teach students what a state machine is... And that can be a good opportunity to talk about what a computer does, because the computer is basically a hardware implementation of a state machine. But that isn't the kind of lesson that you want to be cramming into the middle of the competition season.
- lhecker 3y ago> They're equivalent except that asynchronous callbacks is what actually happens [...] Neither stackful nor stackless coroutines work like this practice. The former suspends coroutines by saving and restoring the CPU state (and stack) and the latter compiles down to state machines, as mentioned in the article. Coroutines are functionally not equivalent to callbacks at all.
- mgaunard 3y agoStackless coroutines are literally the same thing. Stackful coroutines are just a poor man's threads.
- omnicognate 3y ago
- jcarrano 3y agoYes. The callback is not a natural construct (i.e. it does not map well to our intuitive understanding of X is doing something while Y is doing something else). 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)
- vlovich123 3y ago> For example, the KJ library's doc says Just to be clear, the audience for that tour is primarily Cloudflare engineers working on workerd / Workers runtime. Within that context, that's very much correct. The entire codebase is written using asynchronous I/O - synchronous I/O doesn't show up (or if it does, it's in weird parts I've never looked). Fibers are used sparingly in very specific contexts and we have very special code to make it memory efficient at our scale.
- DSingularity 3y agoI interpreted that as “if you can go stackless prefer it for the coroutines-for-elegance use case”.
- z3t4 3y agoI want to add to the callback vs coroutines and async/serial discussion that it all depends on how you treat errors. Are errors mere exceptions or do you want to handle errors in the control flow ? For example turndeg(90), Move(10), PickupItem(), turndeg(180), Move(10) you treat errors as exceptions, if the robot fail to pickup the item, or if it ends up at the wrong place it's an exception. Now if you put all these in try/catch, have the functions return error code (or 0 for success), or use callbacks, the code will be more "ugly" yes, but you then treat errors as "first class citizens", if the robot for example fail to pickup the item you want to try something else, maybe apply more vacum to the suction arm, or switch to a grip arm. And if the robot goes off course you want to make a course direction. async/await, coroutines, futures, promises, do make the code "look nice", but that nice look comes from treating errors as exceptions.
- ptr 3y agoSomething that coroutines made a big impact on for us was testing. Multi-step integration tests became a breeze. With state machines, each test would need its own FSM, and callbacks would make the flow hard to read.
- dools 3y agoI don’t understand why doing this with normal Java is difficult. Just have a list of objectives. Each objective is a class. The autonomous loop picks the next objective from the list and each tick, asks if it has finished, if so get the next objective and so on. 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.
- shadowgovt 3y agoHaving taught FRC students to use Java: when you're talking about people with very little experience programming before, the multiple class abstraction is itself an obstacle to accomplishing the goal. I can't tell you how many times students have gotten frustrated trying to understand why you have to pass arguments into the constructor or, for that matter, Why the constructor is different from other method calls. "But we already said 'drivetrain' in the constructor, and over here in this other class. Why do we have to say m_drivetrain in the class also and do m_drivetrain = drivetrain?" And there isn't actually a better answer than "in other languages that learned from Java's mistakes, you don't. But we happen to be using a language that dates back to when Animaniacs was teaching kids the names all the countries, so some parts are just bad."
- dools 3y ago> the multiple class abstraction is itself an obstacle to accomplishing the goal. Not if your goal is to learn about object orientated programming! > I can't tell you how many times students have gotten frustrated trying to understand why you have to pass arguments into the constructor or, for that matter, Why the constructor is different from other method calls. "But we already said 'drivetrain' in the constructor, and over here in this other class. Why do we have to say m_drivetrain in the class also and do m_drivetrain = drivetrain?" And there isn't actually a better answer than "in other languages that learned from Java's mistakes, you don't. But we happen to be using a language that dates back to when Animaniacs was teaching kids the names all the countries, so some parts are just bad." Do they also have nervous breakdowns every time the spell or read the word "knight"? The etymology of a language can be interesting, and they're welcome to look it up on their own time, but the sooner they learn that all of these decisions are arbitrary, the better. When I was first learning how to use FreeBSD I was equally confounded by trying to apply logic and reason to how the commands looked and worked. Once I just accepted the fact that it was no different to questioning why the buttons were on the left side of the toaster rather than right side, or why sought and sort are two different words pronounced the same my life got a whole lot easier. When I'm teaching kids about code and they ask "why ... " it's an opportunity for them to learn that oh-so-important lesson that in almost all cases the design decisions in software are totally arbitrary or of such obscure etymological origin that, unless you actually want to be a computer science historian, the best answer is "because".
- calf 3y agoThe Command pattern looks like transactional programming which is used in SystemC and similar systems-level programming languages. The idea is that transactions or commands have properties like atomicity and so forth to deal correctly with concurrent behaviors. 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.
- mkoubaa 3y agoIs there such a thing as universally easier to grasp patterns? Maybe there are just brain types that map better to certain programming abstractions and we just have to accept that.
- enum 3y agoVery cool to see. I had worked on something similar but in the context of JavaScript a few years ago (https://arxiv.org/abs/1909.03110 https://arxiv.org/abs/1909.03110). Without coroutines/continuations, it really would have been impossible to get people up to speed in the time we had (one week).
- shadowgovt 3y ago> We were basically creating a crappy programming language out of Java classes. 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.
- Izkata 3y agoThe structure of the coroutine version looks very close to what I've been settling towards for my own background code (not robots but a similar "do a sequence of things that may take different amounts of time and rely on external state"). I'm not sure if it has a name so in my head it's been something like "converging towards a 'good' state": 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.
- TheAlchemist 3y agoGreat article ! Thanks. I will definitely think about this approach when teaching my kids. I'm currently watching some videos by David Beazley, and he does several talks on coroutines in Python - very much recommended.
- scotty79 3y agoMaybe coroutines might become syntax for general hierarchical finite state machines if we manage to implement serialization of current execution state. I'd really love to see async/parallel language based on these ideas.
- vanderZwan 3y agoAs always when this topic comes up, I have to plug Ceu[0] (formerly Céu) and the programming paradigm it represents. It doesn't use coroutines but synchronous concurrency. On top of that it's a reactive language: // 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 https://github.com/ceu-lang/ceu-arduino [1] http://ceu-lang.org/ http://ceu-lang.org/ [2] https://github.com/fsantanna/dceu https://github.com/fsantanna/dceu [3] https://github.com/blech-lang/blech https://github.com/blech-lang/blech
- syncurrent 3y agoRight, imperative synchronous programming simplifies real time processing a lot. Somehow it does not get the attention it should, which is IMHO due to the fact that it is not available in common programming languages. This is why I tried to create DSLs for C and Swift so that more people could potentially play around with that. https://github.com/frameworklabs/proto_activities https://github.com/frameworklabs/proto_activities
- sesuximo 3y agoArguably this devalues the programming they are learning since Coroutines aren’t as generally applicable
- rtpg 3y agoIn a world where most programming languages have async/await style abstractions this stuff is pretty applicable IMO
- erdaniels 3y agoMaybe I'm missing something but why doesn't the Java section right before the Lua section not work? It looks like normal procedural code that can just keep running. The lua version is just one coroutine and it's not yielding to anything else. Is it just a matter of some kind of timing constraint/controller from FRC in the background that need to keep calling myAuto?
- dwohnitmok 3y agoThe robots operate on N ticks per second. You only get one set of robot inputs per tick and can only make one command. The Java code is tickless. During a single tick none of the while conditions will change. Therefore the while loop will run forever and never allow the tick to finish. The Lua code yields at the end of any loop iteration allowing the tick to complete. Then whatever is orchestrating the robot on the next tick calls resume on the coroutine allowing the another iteration to continue, this time with new inputs.
- bvisness 3y agoautonomousPeriodic is a tick function; we need to keep ticking. If we spent more than our allotted 20ms in a single autonomousPeriodic then the robot framework’s safety systems kick in and disable all the motors.
- erdaniels 3y agoAh okay that makes sense, thanks! It may help to make that note near the final java example.
- cryptonector 3y agoYes, threads and co-routines == sequential code, while callbacks == continuation passing style code. Our minds like sequential thinking.
- jezzamon 3y agoI've been playing with JavaScript generators recently, and the ability to "pause" a function is exactly what I'm using them for. The goal is to create an animation of how a maze generation algorithm works, so it lets me write loops like I normally would but allow the function to pause which the website recenders the current state. 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/2b893b3de1d964dcc92023c6318adf05 https://gist.github.com/nicloay/2b893b3de1d964dcc92023c6318a...
- actinium226 3y agoOddly, the kids I mentored in FIRST loved the command/subsystem framework. I kept trying to convince them to do some procedural code to make things simpler (they had little experience coding, even for high school robotics kids), but command/subsystem was their comfort zone and they didn't want to leave it.
- shadowgovt 3y agoIt makes sense for students that can make the logical leap that "things the robot can do" are objects too (which is only a tiny step from the nicely-recursive "code can just be an object"). ... but not everybody is ready for that step.
- taeric 3y agoCoroutines are covered in Knuth's first volume. And, I confess, I think I went years thinking he was just describing method calls. Yes, they were method calls that had state attached, but that felt essentially like attaching the method to an object and calling it a day. 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.
- zooch 3y agoWith these examples I think the author would still be stuck with stepping through the state machines with the students. Unless what you wrote would allow for the "autonomousPeriodic function to keep ticking" another way?
- taeric 3y agoApologies for not making that more explicit. My point was that that isn't necessarily code, but also data. Literally, you can turn that into a list and instead of evaluating it with the standard runtime, you can send it to another place that turns it into the "command" style from the example java. You can /kind/ of do this with java, of course. Just make sure to not use "new FooCommand" and instead change the "foo" function to return a the command object. No reason that couldn't be done; but, and this is the big difference, it requires building a ton of scaffolding in the java program to support both ideas at the same time. In lisp, it is fairly easy to wrap in a macro. Still somewhat magical, I suppose, but no more so than the rest of the compilation/build process. That make sense? I'm somewhat interested in this, so more than happy to try and do a blog post on the idea, if that would help.
- baylessj 3y agoIt's really neat to see some FRC code here, lots to be learned from student robotics competitions! Shameless plug: I work on [PROS](https://github.com/purduesigbots/pros https://github.com/purduesigbots/pros), an open source programming environment for VEX. We've talked about adding coroutine support there, this article is an additional push for getting that done!
- mike_hearn 3y agoAlthough the procedural blocking style is definitely a lot easier, you don't need language support for coroutines to do that. They could have implemented this in Java by just having two threads. One manages the robot and the other main thread blocks whilst waiting for commands to execute. The two threads swap messages using a linked blocking queue.
- pcblues 3y agoI took computer science at school in 1989 because I hated my chemistry teacher in 1988. Never looked back. I really loved the author's call for attention to whether kids are "getting" the concepts being taught and adjust accordingly. 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!"
- nstbayless 3y ago"Deep coroutines:" where you can yield from a function called from the coroutine. Lua supports this, but python doesn't (as far as I can tell). Is there a term for this? 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...
- bvisness 3y agoIn practice we actually do; I had to simplify for the article. We have a few utilities like a `runUntilDone` that make simple sequences easier to write. Example: https://github.com/frc-2175/2023RobotCode/blob/main/src/lua/auto/coroutines.lua#L70-L78 https://github.com/frc-2175/2023RobotCode/blob/main/src/lua/... I suppose we could make more utilities for running coroutines "in parallel", but I haven't really felt the need. At that point we usually have to worry about exit conditions and it feels natural to just write a loop.
- Jtsummers 3y agoFor Python generators, you can yield from called functions by using yield from as in this (quick, not stellar) example: def first(): yield 1 yield from second() yield 4 def second(): yield 2 yield 3 print(list(first())) # collects all the results and prints them # Output: [1, 2, 3, 4] But yeah, it doesn't work on a direct function call you have to know it's going to return a generator (or an iterable, like if it returns a list): def something(): yield from something_else() def something_else(): return [1,2,3,4]
- foxbyte 3y agoI agree, the shift from Java's state machines or "command" system to Lua's coroutines does seem to make the code more intuitive and readable for beginners . Lua's in-built coroutine functionality can be a game-changer for FIRST teams dealing with the complexity of autonomous code 1. It would be fascinating to see how this technique could be applied to other areas of programming where tasks need to be paused and resumed. It's a testament to the versatility of coroutines.