IO refactor continued
Pre-releaseIO<A>
The work has continued following on from the last IO
refactor release. The previous release was less about optimisation and more about correctness, this release is all about making the async/await
state-machines disappear for synchronous operations.
- The core
Run
andRunAsync
methods have been updated to neverawait
. If at any point an asynchronous DSL entry is encountered then processing is deferred toRunAsyncInternal
(which does useawait
). Because,RunAsync
usesValueTask
it's possible to run synchronous processes with next to zero overhead and still resolve to a fully asynchronous expression when one is encountered. - The DSL types have all been updated too, to try to run synchronously, if possible, and if not defer to asynchronous versions.
- DSL state-machine support for resource tracking. It automatically disposes resources on exception-throw
- DSL support for three folding types:
IOFold
,IOFoldWhile
,IOFoldUntil
(see 'Folding') - DSL Support for
Final<F>
(seeFinal<F>
)
TODO
- DSL support for
Repeat*
andRetry*
- then all core capabilities can run synchronously if they are composed entirely of synchronous components.
Folding
The standard FoldWhile
and FoldUntil
behaviour has changed for IO
(and will change for all FoldWhile
and FoldUntil
eventually: it dawned on me that it was a bit of a waste that FoldWhile
was equivalent to FoldUntil
but with a not
on the predicate.
So, the change in behaviour is:
- The
FoldUntil
predicate test (and potential return) is run after the fold-delegate has been run (so it gets the current-value + the fold-delegate updated-state).- This was the previous behaviour
- The
FoldWhile
predicate test (and potential return) is run before the fold-delegate is run (so it gets the current-value + the current-state).
The benefit of this approach is that you can stop a fold-operation running if the state is already 'bad' with FoldWhile
, whereas with FoldUntil
you can exit once the fold-operation makes the state 'bad'. The difference is subtle, but it does give additional options.
Final<F>
trait
Final<F>
is a new trait-type to support try / finally
behaviour. This has been implemented for IO
for now. This will expand out to other types later. You can see from the updated implementation of Bracket
how this works:
public IO<C> Bracket<B, C>(Func<A, IO<C>> Use, Func<Error, IO<C>> Catch, Func<A, IO<B>> Fin) =>
Bind(x => Use(x).Catch(Catch).Finally(Fin(x)));
It's still early for this type, but I expect to provide @finally
methods that work a bit like the @catch
methods.
Unit tests for IO
One of the things that's holding up the full-release of v5
(other than the outstanding bugs in Pipes) is the lack of unit-tests for all of the new functionality. So, I've experimented using Rider's AI assistant to help me write the unit-tests. It's fair to say that it's not too smart, but at least it wrote a lot of the boilerplate. So, once I'd fixed up the errors, it was quite useful.
It's debatable whether it was much quicker or not. But, I haven't really spent any time with AI assistants, so I guess it might just be my inexperience of prompting them. I think it's worth pursuing to see if it can help me get through the unit-tests that are needed for v5
.