From all that I have seen, you are correct that there is no way to read a result from any external variable change and continue where execution left off. That doesn’t mean it is impossible to make reusable functions, just inconvenient at this time. I believe the following would be possible though likely slow:
Consider pistons A, B, C, and Router, where B and C operate as functions. Router knows how to pass a result back to the piston that requested it.
When run with no arguments, A clears the value of its local variables resultB and resultC then executes piston B with whatever args are pertinent, including a caller id to let the Router know to dispatch the result back to A and a callee id to distinguish which result it’s getting back.
B operates on the provided arguments then passes the caller id, callee id, and result to the Router.
The Router has a switch statement mapping caller id to piston (because Execute Piston does not allow a variable piston id). It executes the original caller piston with two args: the callee ID and the result.
Piston A checks args on launch and sees the known callee id; it has been sent the result it requested from piston B. It stores the result to a local variable then calls piston C in the same way, with the same caller ID but a distinct callee id and whatever args C requires.
C gets its own result and sends it back to A via the Router.
A gets the result from C and now has all the data it needs to move forward and act on the two results.
I’m very curious to see how this would work in practice, will try in the morning. The problem space would have to be complex enough to justify all the boilerplate to share routines in this way and also resilient to the asynchronous flow that would result.