Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to return a conduit from an effect? #108

Closed
endgame opened this issue Nov 6, 2022 · 3 comments
Closed

How to return a conduit from an effect? #108

endgame opened this issue Nov 6, 2022 · 3 comments

Comments

@endgame
Copy link

endgame commented Nov 6, 2022

I have been trying to write an effect for the functions in ftp-client-conduit:

data Ftp :: Effect where
  -- Among others
  Retr :: String -> Ftp m (ConduitT Void ByteString m ())

$(makeEffect ''Ftp)

runFtpIO ::
  ( IOE :> es,
    Reader Ftp.Handle :> es,
    Resource :> es
  ) =>
  Eff (Ftp ': es) a ->
  Eff es a
runFtpIO = interpret $ \_ -> \case
  Retr s -> do
    h <- ask
    pure $ Ftp.retr h s

This fails with the following error:

 error:
    • Could not deduce (IOE :> localEs)
        arising from a use of ‘Ftp.retr’
      from the context: (IOE :> es, Reader Ftp.Handle :> es,
                         Resource :> es)
        bound by the type signature for:
                   runFtpIO :: forall (es :: [Effect]) a.
                               (IOE :> es, Reader Ftp.Handle :> es, Resource :> es) =>
                               Eff (Ftp : es) a -> Eff es a
        at <location>
      or from: (HasCallStack, Ftp :> localEs)
        bound by a type expected by the context:
                   EffectHandler Ftp es
        at <location>
      or from: a1 ~ ConduitT Void ByteString (Eff localEs) ()
        bound by a pattern with constructor:
                   Retr :: forall (m :: * -> *).
                           String -> Ftp m (ConduitT Void ByteString m ()),
                 in a case alternative
        at <location>
    • In the second argument of ‘($)’, namely ‘Ftp.retr h ss’
      In a stmt of a 'do' block: pure $ Ftp.retr h ss
      In the expression:
        do h <- ask
           pure $ Ftp.retr h ss
   |
xx |     pure $ Ftp.retr h ss
   |            ^^^^^^^^

It looks to me like we cannot know what's going inside the monad that the ConduitT is applied to, and since @isovector has hit the same problem I think it looks pretty fundamental to the way these effect libraries work.

In effectful's case, it seems like I can get it to compile by being specific about the monad inside the conduit:

data Ftp :: Effect where
   -- I think I like this the best, as we can lift into whatever we need
  Retr :: String -> Ftp m (ConduitT Void ByteString ResIO ())

  -- Constrain the monad
  Retr' :: (MonadIO n, MonadResource n) => String -> Ftp m (ConduitT Void ByteString n ())

  -- Constrain the monad to be an Eff
  Retr'' :: (IOE :> es, Resource :> es) => String -> Ftp m (ConduitT Void ByteString (Eff es) ())

Do you have any alternate suggestions?

@arybczak
Copy link
Collaborator

arybczak commented Nov 6, 2022

Hmm. This should work:

runFtpIO ::
  ( IOE :> es,
    Reader Handle :> es,
    Resource :> es
  ) =>
  Eff (Ftp ': es) a ->
  Eff es a
runFtpIO = interpret $ \env -> \case
  Retr s -> do
    h <- ask
    localLiftUnlift env SeqUnlift $ \lift _unlift -> do
      pure . transPipe lift $ Ftp.retr h s

It looks like this use case warrants addition of localSeqLift and localLift to Effectful.Dynamic.Dispatch so that the unlifting function doesn't have to be redundantly created.

EDIT: with #109:

runFtpIO ::
  ( IOE :> es,
    Reader Handle :> es,
    Resource :> es
  ) =>
  Eff (Ftp ': es) a ->
  Eff es a
runFtpIO = interpret $ \env -> \case
  Retr s -> do
    h <- ask
    localSeqLift env $ \lift -> do
      pure . transPipe lift $ Ftp.retr h s

@endgame
Copy link
Author

endgame commented Nov 6, 2022

Thanks for the quick response, I'd been beating my head against this all day because I somehow missed localLiftUnlift. I put the connection to the FTP server into this handler instead of creating a Reader Ftp.Handle effect, and came up with this:

runFtpIO ::
  forall es a.
  (IOE :> es, Resource :> es) =>
  String ->
  Int ->
  Eff (Ftp ': es) a ->
  Eff es a
runFtpIO host port m = Ftp.withFTP host port $ \h _ -> interpret (handler h) m
  where
    handler :: Ftp.Handle -> EffectHandler Ftp es
    handler h env ftp = localLiftUnlift env SeqUnlift $ \lift _ -> case ftp of
      List ss -> pure . transPipe lift $ Ftp.list h ss
      Retr s -> pure . transPipe lift $ Ftp.retr h s

I'm going to close this now, since the thing I want is possible with current effectful, but I look forward to a release containing #109.

@endgame endgame closed this as completed Nov 6, 2022
@arybczak
Copy link
Collaborator

arybczak commented Nov 9, 2022

FYI 2.2.1.0 is released 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants