How to return a conduit from an effect?
endgame opened this issue · 3 comments
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?
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
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.
FYI 2.2.1.0 is released 🙂