Open eamsden opened 7 years ago
This implementation is wrong. Sorry.
New commit fixes the implementation and it now works as expected.
A simpler implementation is to define this in terms of Pipes.Prelude.toListM
by taking advantage of the fact that ListT
is just a newtype
wrapper around Producer
. I think it would be something like this:
evalListT :: Monad m => ListT m a -> m [a]
evalListT = Pipes.Prelude.toListM . Pipes.enumerate
I would also suggest naming this something like forceListT
to convey to the user that this will not run in constant space
I found myself wishing for this function, for the same reasons that I appreciate Pipes.Prelude.toListM
:
I considered naming it collectListT
, because I think "collect" conveys the warning that all the values get collected at once, thus losing the streaming behavior. This is a word that Java uses for something similar, and I think its meaning is clear in that context.
It can indeed be defined very nicely as toListM . enumerate
. This forces the function to be located in Pipes.Prelude
instead of Pipes
, since toListM
comes from the Pipes.Prelude
module, which imports Pipes
, not the other way around. That seems reasonable enough, because this function probably is not enough of a core idea to go into the library's central module.
So then I started thinking about how this would fit into Pipes.Prelude
, and I have to conclude that it really doesn't. Mostly because I cannot figure out how to justify why only toListM
should have a ListT
variant while all the rest of the functions in the "Folds" section -- fold
, fold'
, foldM
, foldM'
, all
, any
, and
, or
, elem
, notElem
, find
, findIndex
, head
, index
, last
, length
, maximum
, minimum
, null
, sum
, product
, toList
, toListM
, toListM'
-- should not.
Having talked myself out of wanting this function, I'm now of the opinion that there are a handful of documentation opportunities here instead:
ListT
might emphasize that use of this type very often involves using Select
and enumerate
to convert back and forth with Producer
, and that when you're perusing the API for ways to construct a ListT
value and for things that you can do with a ListT
value, you should not forget to consider all the Producer
utilities in Pipes.Prelude
.runListT
might emphasize that it discards all the values, suggest that if you need to "get" the values you should apply enumerate
and look at the "Folds" section in Pipes.Prelude
, and directly address the example of (toListM . enumerate)
and discuss why that sacrifices streaming.Pipes.Tutorial
might benefit from the addition of a section about folds, which would be another place to discuss the situations when folding a Producer
is or is not appropriate, and another place to mention that if you want to fold a ListT
, the first thing you're going to want to do is apply enumerate
and then from there think about how you want to fold the Producer
using an appropriate Pipes.Prelude
function.(Select . each)
is another idiom that ought to be highlighted more. Pipes.Tutorial
does mention this one, but perhaps it should also appear in the haddock somewhere in the Pipes
module.@chris-martin: If you put up a pull request, I'd accept it
Lovely! I intend to.
The proper way to use
ListT
in pipes is to have an underlying monad and effect per choice through that monad. However, sometimes it's really convenient to run aListT
computation and then get the results in the underlying monad as a list. Since the MTL'sListT
is more restrictive, many use cases for this functionality would be helped by using pipes'ListT
, while still obtaining the results as a list.