michaelt / streaming

An optimized general monad transformer for streaming applications, with a simple prelude of functions
BSD 3-Clause "New" or "Revised" License
104 stars 11 forks source link

catMaybes and mapMaybe #9

Closed andrewthad closed 8 years ago

andrewthad commented 8 years ago

I've written two functions that I need for a project I'm working on. I think these could be added to Streaming.Prelude as catMaybes and mapMaybe, since they are analogous to the functions from Data.Maybe with the same names. I don't think any strictness annotations are required, but I'm not really good at knowing when to use them.

streamingCatMaybes :: Monad m => Stream (Of (Maybe a)) m r -> Stream (Of a) m r                   
streamingCatMaybes = loop where                                                                   
  loop stream = case stream of                                                                    
    Streaming.Return r -> Streaming.Return r                                                      
    Streaming.Effect m -> Streaming.Effect (liftM loop m)                                         
    Streaming.Step (ma :> snext) -> case ma of                                                    
      Nothing -> loop snext                                                                       
      Just a -> Streaming.Step (a :> loop snext)                                                  

streamingMapMaybe :: Monad m => (a -> Maybe b) -> Stream (Of a) m r -> Stream (Of b) m r          
streamingMapMaybe phi = loop where                                                                
  loop stream = case stream of                                                                    
    Streaming.Return r -> Streaming.Return r                                                      
    Streaming.Effect m -> Streaming.Effect (liftM loop m)                                         
    Streaming.Step (a :> snext) -> case phi a of                                                  
      Nothing -> loop snext                                                                       
      Just b -> Streaming.Step (b :> loop snext)                                                  
michaelt commented 8 years ago

Hi, @andrewthad I had thought of catMaybes before. I didn't include it because I was closely following the Pipes.Prelude which assimilates this to concat :: Foldable t => Stream (Of (t a)) -> Stream (Of a) m r (as we would put it here). Now that the API is clearer, though, it seems it would be better to include catMaybes and mapMaybe after all. First, I would sort of like the Streaming.Prelude to be as close as possible to the list handling in Prelude Data.List and the other basic base modules etc. Then you can use it without thinking about clever identifications like that, and without mastering the api as a whole, and getting involved with a 'streaming IO framework' and figuring out what obvious things from Prelude etc. might have turned into. Interestingly, the new type of Prelude.concat :: Foldable t => t [a] -> [a] would lead one away from suspecting that Pipes/Streaming.concat would work the way it does; the order of t and [] should be swapped. Second, implemented you have done above, mapMaybe will definitely be a bit faster than S.concat . S.map f ; and catMaybe maybe faster in some contexts by being specialized and direct.

You can turn catMaybe and mapMaybe into a patch if you like, or I can write them in. In general all recursive functions are marked with an {-#INLINABLE mapMaybe #-} pragma. That way the source can gravitate toward main and the compiler has a lot of chances to see through the program as a whole. (This too follows the approach of pipes)

Thanks!

andrewthad commented 8 years ago

Thanks for the background on that. It's helpful for understanding motivations and goals. I'll put up a PR in the next half hour.