However, you could instead use an abstract data type based on a newtype declaration:
module Follow (
Follow, follow,
openLog, addLog, saveLog, ...
) where
{-- assorted imports -}
newtype Follow a = Follow (IO a)
follow :: Follow a -> IO ()
openLog :: Filepath -> Follow LogFile
addLog :: LogFile -> Message -> Follow ()
saveLog :: LogFile -> Follow ()
⋮
As always, it will depend on what’s required - sometimes an ADT will suffice; other situations may require the granularity provided by the tracking of individual effects. Just remember that there can be more that one way to achieve a result.