Question: how invole sendMssage on IO () scope
Closed this issue · 6 comments
Trying to reply @Text message myVal
inside just IO ()
method
I'm starting some separated polling loop the same time with a bot and want to have ability to call bot functionality such sendMessage etc there too.
• Couldn't match expected type: IO a0
with actual type: Polysemy.Internal.Sem
r0 (Either RestError Message)
as far a I understand I still can do it with polysemy eval?
please provide minimal example
One way is to some some function like the following:
bindSemToIO :: forall r p a. P.Member (P.Final IO) r => (p -> P.Sem r a) -> P.Sem r (p -> IO (Maybe a))
bindSemToIO m = P.withStrategicToFinal $ do
istate <- P.getInitialStateS
m' <- P.bindS m
ins <- P.getInspectorS
P.liftS $ pure (\x -> P.inspect ins <$> m' (istate $> x))
This works by capturing the state of the Sem
and packaging it into the final monad.
(The reason for the Maybe
is that a Sem a
doesn't always yield an a
if an exception effect is in use)
Using this is most likely fine because you can't use plain State
effects with calamity anyway, but be aware that if you use this anywhere else local state changes won't escape the IO
packaged action and every time you execute the action any local state will be the same as it was when the state was initially captured.
I am very sorry but this looks way too complicated for me to understand this code and usage.
that's how I was trying to use it :-/
import Control.Applicative
import Control.Monad
import Optics
import Optics.TH
import Calamity
import Data.Text (Text)
import qualified Data.Text as T
import Polysemy (Member)
import qualified Polysemy as P
import qualified Polysemy.Embed as P
import qualified Polysemy.Final as P
import qualified Polysemy.State as P
bindSemToIO ∷ forall r p a. P.Member (P.Final IO) r
=> (p -> P.Sem r a)
-> P.Sem r (p -> IO (Maybe a))
bindSemToIO m = P.withStrategicToFinal $ do
istate <- P.getInitialStateS
m' <- P.bindS m
ins <- P.getInspectorS
P.liftS $ pure (\x -> P.inspect ins <$> m' (istate $> x))
replyBotEff ∷ Snowflake Channel
-> Snowflake Message
-> String
-> IO ()
replyBotEff _chanId mesgId stxt =
void $ bindSemToIO
$ void $ reply @Text mesgId (T.pack stxt)
Ah it's a bit weird to use but here's how it works:
The parameter to the function (p -> P.Sem r a)
should be some function that takes a parameter and returns a polysemy action, so something like \(id, text) -> void $ reply id text
(which will have the type P.Member ... r => (Snowflake Channel, Text) -> P.Sem r ...
Using this bindSemToIO (\(id, text) -> ...)
will get you something with the type P.Member ... r => P.Sem r ((Snowflake Channel, Text) -> IO (Maybe ...))
You can then use that action inside some Sem
context to get your (Channel, Text) -> IO (Maybe ...)
that you can then pass around wherever you want and use within an IO
context.
foo :: P.Sem r ()
foo = do
act <- bindSemToIO (\(id, text) -> void $ reply id text)
P.embed . forkIO $ do
act foo bar
The Sem r (p -> IO (Maybe a))
action serves to collect the state of the Sem
and package it up in an IO
still wasn't able to do it, need to convert message from snowflake, many types errors
can't just do
kreply ∷ P.Members '[P.Fail] r
=> (Snowflake Message, T.Text) -> P.Sem r ()
kreply (target, txt) = void $ reply @Text target txt
• Could not deduce (P.Member
(DiPolysemy.Di
df1-0.4.1:Df1.Types.Level
df1-0.4.1:Df1.Types.Path
df1-0.4.1:Df1.Types.Message)
r)
arising from a use of ‘reply’
from the context: P.Members '[P.Fail] r
bound by the type signature for:
kreply :: forall (r :: P.EffectRow).
P.Members '[P.Fail] r =>
(Snowflake Message, Text) -> P.Sem r ()
Oops I forgot the type of reply
You can just pass Message
instead of Snowflake Message
The type error here is because you're missing an effect reply
needs from the effects in P.Members '[P.Fail] r
It's not possible to get hold of a (Message, Text) -> IO ()
without first having the bot start up, as the token and other state used by reply
won't be available until after the bot starts.
If you're starting up some message bus consumer and need to send messages from it, you could start the loop from within the bot context like this:
runBotIO (BotToken ...) intents $ do
replyIO <- bindSemToIO (\(c, t) -> void $ reply c t)
P.embed $ forkIO $ startMyLoopWithCallback replyIO
-- ...
now I understand better. but I don't have Message, only ID (Snowflake Message), is it possible to get Message by having only numbers of message channel user...?
guess I need to use Calamity.Cache.Eff , or is there other possible ways? I also have Snowflake Channel and Guild if it may help
for now solved with
replyWithSnowflake ∷ (BotC r, HasID Channel Message)
=> (Snowflake Message, Text)
-> P.Sem r ()
replyWithSnowflake (msgId, txt) = do
maybeMsgFromId <- getMessage msgId
case maybeMsgFromId of
Just msgFromId -> void $ reply @Text msgFromId txt
Nothing -> pure ()
and generally it works! thank you