
Well, if pipelining only the outermost layer is enough to improve performance of your code, perhaps you could just move `yield` outside all the `with...` functions, so everything stays in IO until it's ready to yield the result: solveTraj :: Factory -> Geometry -> Detector -> Sample -> Pipe Engine Geometry IO () solveTraj f g d s = do e <- await let name = engineName e solution <- withSample s $ \sample -> withDetector d $ \detector -> withGeometry f g $ \geometry -> withEngineList f $ \engines -> withCString name $ \cname -> do c_hkl_engine_list_init engines geometry detector sample engine <- c_hkl_engine_list_engine_get_by_name engines cname nullPtr n <- c_hkl_engine_pseudo_axis_names_get engine >>= darrayStringLen return $ solve' engine n e >>= getSolution0 yield solution This can be further simplified to use the version of `mapM` from Pipes.Prelude (https://hackage.haskell.org/package/pipes-4.1.8/docs/Pipes-Prelude.html) mapM :: Monad m => (a -> m b) -> Pipe a b m r This way your code would look more or less like it looked without pipes, something like: import qualified Pipes.Prelude as Pipes runEffect $ for (each engines) >-> Pipes.mapM (solve' engine n e >>= getSolution0) >-> P.print Best regards, Marcin Mrotek