Live data from Hacker News

Coroutines make robot code easy

bvisness.me

21–30 of 127 posts

Re: Coroutines make robot code easy

#21

I 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://…

I’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.

Re: Coroutines make robot code easy

#22
Yes. 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)

Re: Coroutines make robot code easy

#23
post #11

Similar code could be written in C#, with code like IEnumerable MyRobotBrain() { while(drivetrain.getDistanceInches() > -48) { yield drivetrain.arcadeDrive(0.5, 0); } yield shooter.shoot(); }

Dots instead of colons ?

Yes good catch; oversight from posting on mobile. I've fixed it up

Re: Coroutines make robot code easy

#24
Something 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.

Re: Coroutines make robot code easy

#25
post #10

leaky 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.

Coroutines are an abstraction though.

Re: Coroutines make robot code easy

#26

I 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://…

> 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.

Years ago I attempted to build an experimental language with first-class resumable functions. Every function can be invoked by the caller through a special reference type called "quaint". The caller can resume or stop the execution of the function either after reaching a timeout, or after passing a "wait label":

https://github.com/bbu/quaint-lang

A typical CPU-intensive example where preemption is done by the caller after a certain timeout:

    entry
    {
        fibq: quaint(u32) = ~fibonacci(32 as u32);

        ps("At start: "), pu8(fibq@start), pnl();
        iter: u32 = 0:u32;

        do {
            wait fibq for 1000 msec;
            ps("Iteration "), pu32(iter++), pnl();
        } while !fibq@end;

        ps("At end: "), pu8(fibq@end), pnl();
        const value: u32 = *fibq;
        ps("Reaped value: "), pu32(value), pnl();
    }

    fibonacci(number: u32): u32
    {
        if number == 0:u32 || number == 1:u32 {
            return number;
        } else {
            return fibonacci(number - 1:u32) + fibonacci(number - 2:u32);
        }
    }
An example that uses "wait labels" to suspend execution of the callee at certain points:

    entry
    {
        q: quaint(u64) = ~pointless_function();

        wait q until pointless_function::label_a;
        ps("At label_a: "), pu8(q@pointless_function::label_a), pnl();

        wait q until pointless_function::label_b;
        ps("At label_b: "), pu8(q@pointless_function::label_b), pnl();

        wait q until pointless_function::label_c;
        ps("At label_c: "), pu8(q@pointless_function::label_c), pnl();

        wait q;
        ps("At end: "), pu8(q@end), pnl();

        ps("Result: "), pu64(*q), pnl();
    }

    pointless_function: u64
    {
        i: u64 = 0 as u64;

        while ++i 

Re: Coroutines make robot code easy

#27

Yes. 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…

I interpreted that as “if you can go stackless prefer it for the coroutines-for-elegance use case”.

Re: Coroutines make robot code easy

#28

Yes. 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…

I 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.

Re: Coroutines make robot code easy

#29

People 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.

> 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.

Re: Coroutines make robot code easy

#30
I 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.

Post reply on HN