FSharpPlus icon indicating copy to clipboard operation
FSharpPlus copied to clipboard

Proper SeqT implementation

Open gusty opened this issue 3 years ago • 1 comments

The previous implementation was not adding any effect besides binding strategy. With this implementation we can derive both asyncSeq as type AsyncSeq<'T> = SeqT<Async<bool>, 'T> and taskSeq as type TaskSeq<'T> = SeqT<Task<bool>, 'T> among combinations with other monads like type AsyncResultSeq<'T, 'Error> = SeqT<ResultT<Async<Result<bool, 'Error>>, 'T> .

This adds the interfaces IEnumeratorM<'Monad<bool>, 'T> and IEnumerableM<'Monad<bool>, 'T> which would ideally be an alias of SeqT<'Monad<bool>, 'T> but since as of F# 6 we can't create static members in interfaces we will have to rely in composition instead.

gusty avatar Jul 28 '22 07:07 gusty

This is almost Ready for review. Here's what's still pending:

  • [x] Reduce number of constraints
  • [x] Add some docs with sample usage
  • [x] Review naming for M functions like iterM, mapM, unfoldM and so on
  • [x] Bring some AsyncSeq tests
  • [ ] See if it's worth adding some specific liftable functions for tasks
  • [ ] Add support for use! (but maybe in a separate PR)

We won't be adding all AsyncSeq functions as some of them are very specific to Async.

gusty avatar Aug 08 '22 06:08 gusty

Regarding naming:

  • unfold should be unfoldM, then we should add unfold.
  • iter, iterM and mapM are consistent, although they don't match the generic iter and mapM but they are not intended to be generalized, Haskell's ListT have similar names and they are also not generalized.
  • bindM in fact is like lift them bind, therefore renamed to bindLift althogh it may remain internal.
  • toSeqM is run that was eveident in the code, so toListM and toArrayM can be renamed as runToList and runToArray.

gusty avatar Aug 26 '22 16:08 gusty

type TaskSeq<'T> = SeqT<Task<bool>, 'T>

I recently experienced quite some issues where Task was in an eager sequence. I wonder how that plays out here. Since tasks are hot-started and everything. I solved my issues by wrapping stuff into the taskSeq of Don Syme, which is basically wrapping it in an IAsyncEnumerable, which I see you're also using here, but only for async sequences. It may be worth testing the task-seq code with significantly large sequences of tasks in lists, for instance (which is where I had the issue).

I should probably go over your code in a little bit more detail, it's certainly very interesting!

toSeqM is run that was eveident in the code, so toListM and toArrayM can be renamed as runToList and runToArray.

not sure if the terminology of run would be clear here? toXXX and ofXXX seem to be the more common naming conventions for conversion from/to types.

abelbraaksma avatar Aug 26 '22 17:08 abelbraaksma

I wonder how that plays out here. Since tasks are hot-started and everything. I solved my issues by wrapping stuff into the taskSeq of Don Syme, which is basically wrapping it in an IAsyncEnumerable, which I see you're also using here,

Not really, I'm using a generic IEnumerableM<_, _> but when specialized it behaves in a similar way to IAsyncEnumerable<_>, then there are some functions to convert back and forth to IAsyncEnumerable<_>.

but only for async sequences.

Nope, it's generic so it works with both async and tasks.

It may be worth testing the task-seq code with significantly large sequences of tasks in lists, for instance (which is where I had the issue).

Absolutely, but I think both implementations relies on the fact that a strict monad (task) combined with a lazy one (seq) results in a lazy combination.

I should probably go over your code in a little bit more detail, it's certainly very interesting!

Sure, thanks !

not sure if the terminology of run would be clear here? toXXX and ofXXX seem to be the more common naming conventions for conversion from/to types.

Well that's a bit tricky I know. The context is that the run functions are standard in this library and they all mean unwrap the monad transformer, so for instance if you have async with option, you wrap them in OptionT, when you run it you get back the async<option<_>>, here in SeqT you get async<seq <_>>, and the other functions are like running run and then mapping toX to the desired collection.

In the original asyncSeq library these functions are not generic so naming them is easier, they are called toSeqAsync and so one, here initially I name them toSeqM by applying the initial renaming pattern of async to M, but I don't think those name are clear enough either. I'm open to suggestions.

gusty avatar Aug 27 '22 04:08 gusty