Skip to content

Conversation

@aquarial
Copy link
Collaborator

All the existing code can change import Discord into import DiscordMonadTransformerLibrary and will compile and run correctly.

New code (and examples) can use the new import Discord to get IO () based functions, instead of DiscordHandler = ReaderT DiscordHandle IO.

The long and explicit name is make import Discord the clear default UI to the library, so it's easy as possible for those newer to the haskell language.

An alternate design I considered was leaving import Discord as is, and adding a Discord.IO module for the IO utilities. This is nice because old code works with no change, but could be confusing.

@aquarial
Copy link
Collaborator Author

Using IO () instead of Discord Handler () means normal forkIO, newMVar, newIORef, etc concurrency functions compose with the library, without needing the external UnliftIO concurrency lib.

The disadvantage is that the handle var will be passed around to everything that uses the library. I think this is ok because it's easier for me, and matches other IO UI like the file handler interface: https://stackoverflow.com/questions/9406463/withfile-vs-openfile

@L0neGamer
Copy link
Contributor

L0neGamer commented Mar 22, 2022

I dislike this change for two reasons.

  1. Supporting two methods of calling the bot and it's functions just mean that there will be increased confusion about which function to use where, as opposed to simplifying the process, as well as doubling points of failure.
  2. Using the MTL setup, it's clear when someone is calling a non-discord-haskell function. DiscordHandler a effectively says "any functions you call within this monad will be safe - if you want to introduce outside IO, that's on you". It guarantees that there will be a correct and whole DiscordHandle to use.

Not sure if it exists already, but a function like withDiscordHandle :: forall a f. (Returns f ~ DiscordHandler a) => DiscordHandle -> f -> IO a would allow the IO component to be more easily handled, potentially. Then you could have a selection of ' functions which operate within IO, and just call the non-io functions underneath.

In the case of needing unliftio as an external dependency, couldn't discord-haskell variants of lifts be exported that, under the hood, are just calls to unliftio?

Regardless, I think this would be a step back for the library.

@aquarial
Copy link
Collaborator Author

Supporting two methods of calling the bot and it's functions just mean that there will be increased confusion

Yeah, it's a price of backwards-compatibility. Removing all the MTL stuff would be simpler, but would require more work for all the code written today. Saving backwards compatibility requires supporting two versions of the code.

find . | grep '\.hs$' | while read f; do sed -i 's/^import Discord$/import DiscordMonadTransformerLibrary/' "$f" ; done

Using the MTL setup, it's clear when someone is calling a non-discord-haskell function. DiscordHandler a effectively says "any functions you call within this monad will be safe - if you want to introduce outside IO, that's on you". It guarantees that there will be a correct and whole DiscordHandle to use.

I don't think this changes anything. DiscordHandler a is simply a ReaderT around the handle. It has no other guarantees, besides access to the handle. This is the same as the new functions of the form DiscordHandle -> IO ()

withDiscordHandle :: forall a f. (Returns f ~ DiscordHandler a) => DiscordHandle -> f -> IO a

~> :i runReaderT 
type role ReaderT representational representational nominal
type ReaderT :: * -> (* -> *) -> * -> *
newtype ReaderT r m a = Control.Monad.Trans.Reader.ReaderT {runReaderT :: r -> m a}
        -- Defined in ‘transformers-0.5.6.2:Control.Monad.Trans.Reader’

-- specialized to discord
runReaderT  :: DiscordHandler () -> DiscordHandle -> IO ()

In the case of needing unliftio as an external dependency, couldn't discord-haskell variants of lifts be exported that, under the hood, are just calls to unliftio?

True. discord-haskell could also re-export the unliftio stuff. The current version re-exports Data.Default (def)

Another thing in favor of your point is that importing unliftio fixes my pain point with the MTL stuff. I really don't like liftIO $ takeMVar state in the state example, but that's because it imports Control.Concurrent instead of UnliftIO.Concurrent. the unliftio versions of forkIO, newMVar, etc all work with a DiscordHandler ReaderT.


Roughly my conclusion is that if I could go back I would not have introduced the ReaderT. But since it's here, it's not big enough of a problem to justify breaking backwards compatibility to change it.

The best solution is probably adding more docs about ReaderT to the readme.

@aquarial
Copy link
Collaborator Author

aquarial commented Apr 4, 2022

Closing this, as mentioned.

Might add some notes about the ReaderT into the Readme.

@aquarial aquarial closed this Apr 4, 2022
@aquarial aquarial mentioned this pull request Apr 9, 2022
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

Successfully merging this pull request may close these issues.

3 participants