Support for generalised monad tail-recursion #1526
louthy
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Quite a big release here. It's not had a huge amount of testing, but thought it would be good to get it out before I disappear for Christmas, in case anyone wants to have a play with the new features.
Monad.RecurAs suggested here and at various times over the years, we could support the same
tailRecMpattern as seen in Cats and Scalaz libraries from the Scala ecosystem. I have been a little sceptical of the approach, mostly because it forces the user to manually trampoline. I wasn't keen on going all in, but then I woke up Saturday morning thinking about it (that's how sad my life is) and felt inspired to implement it.150+ file changes later and we now have support.
tailRecMin Scala becomesMonad.recurin language-ext. Every monad is expected to implement theMonad.Recurtrait method, but you could also use the placeholder function (Monad.unsafeRecur) that still uses regular recursion. This is because:IO<A>), so using regular recursion is fineThere's also two other helper functions:
Monad.iterableRecurandMonad.enumerableRecurwhich are defaultMonad.Recurimplementations for collections.Here's the implementation of
Monad.RecurforOption:What's really nice about this implementation is how 'light' it is. It's a simple while-loop with a test to see if we're done:
if(mnext.IsDone)or a continuation-value (mnext.Loop) to be passed to the 'bind' functionf. As well as the standardOptionmonad-bind logic of earlying-out when in aNonestate.It's certainly not very elegant in C#, I would mostly advise using maps, folds, and traversals (where possible). But there's always that one time where recursion would be better, so now you can guarantee its safety.
I still want to build support for tail-recursion into the 'control' monads (those that are lazy computations and therefore can be backed by an interpreted DSL. I still believe that's more effective and elegant.
Iterablenow supportsIAsyncEnumerableSo
Iterablewhich started life as a wrapper forIEnumerablenow supports both synchronous and asynchronous lazy streams. This is continuing the goal of killingTaskandasync/await. Only when you use anIterabledo you care how you consume it. If you want asynchronicity then you can either callAsAsyncEnumerable()or use any of the*IOsuffixed methods (likeCountIO,FoldIO, etc.)IterableNEis a new non-emptyIterableEvery wanted to work with a lazy stream that must have at least one item in it? Well, now you can:
IterableNE. It's impossible for anIterableNEto be constructed without at least one item in it, so you can assume it will always have aHeadvalue.It also supports synchronous and asynchronous sequences.
Applicative.Actionsin theApplicativetrait updatedThere were previously two methods:
Now there's one
Actionswould previously throw exceptions with empty sequences.Monad bind operator
Previous change to change the monad bind operator from
>>to >>>` have been reverted. It just didn't 'feel' right, so we're back to where we were.Lots of other minor tweaks
There are lots of other minor tweaks, but it's now 1am on Christmas day, so I'm going to stop typing! Happy (belated) Hanukkah, Merry Christmas, and/or Happy Holidays to you all.
This discussion was created from the release Support for generalised monad tail-recursion.
Beta Was this translation helpful? Give feedback.
All reactions