{"action":"created","issue":{"url":"https://api.github.com/repos/snoyberg/conduit/issues/389","repository_url":"https://api.github.com/repos/snoyberg/conduit","labels_url":"https://api.github.com/repos/snoyberg/conduit/issues/389/labels{/name}","comments_url":"https://api.github.com/repos/snoyberg/conduit/issues/389/comments","events_url":"https://api.github.com/repos/snoyberg/conduit/issues/389/events","html_url":"https://github.com/snoyberg/conduit/issues/389","id":355312626,"node_id":"MDU6SXNzdWUzNTUzMTI2MjY=","number":389,"title":"Can we generalize the Data.Conduit.Lift functions?","user":{"login":"mbwgh","id":28275377,"node_id":"MDQ6VXNlcjI4Mjc1Mzc3","avatar_url":"https://avatars3.githubusercontent.com/u/28275377?v=4","gravatar_id":"","url":"https://api.github.com/users/mbwgh","html_url":"https://github.com/mbwgh","followers_url":"https://api.github.com/users/mbwgh/followers","following_url":"https://api.github.com/users/mbwgh/following{/other_user}","gists_url":"https://api.github.com/users/mbwgh/gists{/gist_id}","starred_url":"https://api.github.com/users/mbwgh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mbwgh/subscriptions","organizations_url":"https://api.github.com/users/mbwgh/orgs","repos_url":"https://api.github.com/users/mbwgh/repos","events_url":"https://api.github.com/users/mbwgh/events{/privacy}","received_events_url":"https://api.github.com/users/mbwgh/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":8,"created_at":"2018-08-29T20:05:16Z","updated_at":"2018-08-30T08:50:15Z","closed_at":"2018-08-30T08:50:15Z","author_association":"NONE","body":"The Data.Conduit.Lift module provides helper functions for working with monad transformers.\r\nCurrently, this seems to go something like this:\r\n\r\n```haskell\r\nconduit1 :: ConduitT A B IO ()\r\nconduit2 :: ConduitT B C (ReaderT R IO) ()\r\n\r\ncomposed :: ConduitT A C (ReaderT R IO) ()\r\ncomposed = (readerC . const) conduit1 .| conduit2\r\n```\r\n\r\nThis starts to become unsatisfying when you define a typeclass involving `ConduitT` in the\r\ntype signature of it's methods. A contrived example:\r\n\r\n```haskell\r\nclass Monad m => MonadFileSystem m where\r\n  sourceFile :: FilePath -> ConduitT i ByteString m ()\r\n  sinkFile :: FilePath -> ConduitT ByteString o m ()\r\n```\r\n\r\nIf such a thing were to be even remotely useful, it should come with the corresponding mtl-boilerplate.\r\n\r\n```haskell\r\ninstance MonadFileSystem m => MonadFileSystem (ReaderT r m) where\r\n  sourceFile fp = readerC $ \\_ -> sourceFile fp\r\n  sinkFile fp = readerC $ \\_ -> sinkFile fp\r\ninstance MonadFileSystem m => MonadFileSystem (ExceptT e m) where\r\n  sourceFile fp = exceptC $ fmap pure $ sourceFile fp\r\n  sinkFile fp = exceptC $ fmap pure $ sinkFile fp\r\n```\r\n\r\nLet's nope right outta there. If the `MonadFileSystem` class were instead defined as\r\n\r\n```haskell\r\nclass Monad m => MonadFileSystem m where\r\n  readFile :: FilePath -> m ByteString\r\n  writeFile :: FilePath -> ByteString -> m ()\r\n```\r\n\r\nthen it would be \"well-behaved\" in the sense that the `m` only shows up in covariant position and writing instances would be as simple as using `lift`.\r\n\r\nIf we were to define a similar typeclass like `MonadTrans`, things would be way easier:\r\n\r\n```haskell\r\nclass MonadCTrans t where\r\n  clift :: Monad m => ConduitT i o m a -> ConduitT i o (t m) a\r\n\r\n-- Compare to:\r\nclass MonadTrans t where\r\n  lift :: Monad m => m a -> t m a\r\n\r\ninstance MonadCTrans IdentityT where\r\n  clift = identityC\r\n\r\ninstance MonadCTrans MaybeT where\r\n  clift = maybeC . fmap pure\r\n\r\ninstance MonadCTrans (ReaderT r) where\r\n  clift = readerC . const\r\n\r\ninstance MonadCTrans (ExceptT e) where\r\n  clift = exceptC . fmap pure\r\n\r\ninstance Monoid w => MonadCTrans (WriterT w) where\r\n  clift = writerC . fmap (, mempty)\r\n\r\ninstance MonadCTrans (StateT s) where\r\n  clift x = stateC $ \\s -> (, s) <$> x\r\n\r\ninstance MonadCTrans ResourceT where\r\n  clift = resourceC . const\r\n\r\n-- And so on...\r\n\r\n-- This function could be considered \"missing\".\r\nidentityC ::\r\n  Monad m => ConduitT i o m r -> ConduitT i o (IdentityT m) r\r\nidentityC = transPipe lift\r\n\r\n-- I am not sure about this one, but at least it doesn't require importing an Internal module.\r\nresourceC ::\r\n  Monad m =>\r\n  (InternalState -> ConduitT i o m b) -> ConduitT i o (ResourceT m) b\r\nresourceC k = do\r\n  s <- lift getInternalState\r\n  transPipe lift (k s)\r\n```\r\n\r\nNow we can \"lift conduits\" in the way we are used to from `MonadTrans` instances. The boilerplate for our pass-through typeclass instances now reduces to:\r\n\r\n```haskell\r\nclass Monad m => MonadFileSystem m where\r\n  sourceFile :: FilePath -> ConduitT i ByteString m ()\r\n  sinkFile :: FilePath -> ConduitT ByteString o m ()\r\n\r\n  default sourceFile :: (MonadCTrans t, MonadFileSystem m', t m' ~ m) => FilePath -> ConduitT i ByteString m ()\r\n  sourceFile = clift . sourceFile\r\n  default sinkFile :: (MonadCTrans t, MonadFileSystem m', t m' ~ m) => FilePath -> ConduitT ByteString o m ()\r\n  sinkFile = clift . sinkFile\r\n\r\ninstance MonadFileSystem m => MonadFileSystem (IdentityT m)\r\ninstance MonadFileSystem m => MonadFileSystem (MaybeT m)\r\ninstance MonadFileSystem m => MonadFileSystem (ReaderT r m)\r\ninstance MonadFileSystem m => MonadFileSystem (ExceptT e m)\r\ninstance (Monoid w, MonadFileSystem m) => MonadFileSystem (WriterT w m)\r\ninstance MonadFileSystem m => MonadFileSystem (StateT s m)\r\ninstance MonadFileSystem m => MonadFileSystem (ResourceT m)\r\n```\r\n\r\nIn similar spirit, the `combined` function from above would be written as\r\n\r\n```haskell\r\ncombined = clift conduit1 .| conduit2\r\n```\r\n\r\nDo you think something like this would be useful?"},"comment":{"url":"https://api.github.com/repos/snoyberg/conduit/issues/comments/417241459","html_url":"https://github.com/snoyberg/conduit/issues/389#issuecomment-417241459","issue_url":"https://api.github.com/repos/snoyberg/conduit/issues/389","id":417241459,"node_id":"MDEyOklzc3VlQ29tbWVudDQxNzI0MTQ1OQ==","user":{"login":"mbwgh","id":28275377,"node_id":"MDQ6VXNlcjI4Mjc1Mzc3","avatar_url":"https://avatars3.githubusercontent.com/u/28275377?v=4","gravatar_id":"","url":"https://api.github.com/users/mbwgh","html_url":"https://github.com/mbwgh","followers_url":"https://api.github.com/users/mbwgh/followers","following_url":"https://api.github.com/users/mbwgh/following{/other_user}","gists_url":"https://api.github.com/users/mbwgh/gists{/gist_id}","starred_url":"https://api.github.com/users/mbwgh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mbwgh/subscriptions","organizations_url":"https://api.github.com/users/mbwgh/orgs","repos_url":"https://api.github.com/users/mbwgh/repos","events_url":"https://api.github.com/users/mbwgh/events{/privacy}","received_events_url":"https://api.github.com/users/mbwgh/received_events","type":"User","site_admin":false},"created_at":"2018-08-30T08:50:15Z","updated_at":"2018-08-30T08:50:15Z","author_association":"NONE","body":"Okay, I will submit a PR then. Closing since my initial question has been answered (thanks by the way)."}}
{"actor":{"display_login":"mbwgh"}}