0

I am attempting to build a slackbot using this library: https://hackage.haskell.org/package/slack-api, just to learn a little bit more haskell, and hopefully, finally understand monads -_-.

I then have the following types:

data BotState = BotState
  { 
    _appState :: AppState
  }

makeLenses ''BotState


type AppState = HM.Map String ChannelState

emptyState :: AppState
emptyState = HM.empty

data ChannelState = ChannelState
{ _counter :: Int}

type Bot = Slack.Slack BotState

and I run my bot with:

initApp = lookupEnv "SLACK_API_TOKEN" >>=
  \apiToken -> case apiToken of
    Nothing -> throwM ApiTokenMissingException
    Just t -> void $ Slack.runBot (Slack.SlackConfig t) runApp $ BotState emptyState

where:

runApp :: Slack.Event -> Bot ()
runApp m@(Slack.Message cid uid body _ _ _) = sendMessage cid "GAH I CAN HAZ CHZBURGHER!" 

This runs fine, now I wish to add the ability to update the system state (by incrementing the counter, or in other ways).

so I add a modifyState function to my Bot:

modifyState :: (AppState -> AppState) -> Bot ()
modifyState f = uses Slack.userState $ view appState >>=
  \state -> modifying Slack.userState $ set appState $ f state 

This breaks with:

 No instance for (Control.Monad.State.Class.MonadState
                           (Slack.SlackState BotState) ((->) BotState))
          arising from a use of ‘modifying’
        In the expression: modifying Slack.userState
        In the expression:
          modifying Slack.userState $ set appState $ f state
        In the second argument of ‘(>>=)’, namely
          ‘\ state -> modifying Slack.userState $ set appState $ f state’

Which makes sense given the signature for modifying:

modifying :: MonadState s m => ASetter s s a b -> (a -> b) -> m ()

However, upon looking at the documentation for Slack.userState:

userState :: forall s s. Lens (SlackState s) (SlackState s) s s Source

And then:

data SlackState s

 ... Constructor ...
    Instances
Show s => Show (SlackState s)Source  
MonadState (SlackState s) (Slack s)Source   

So then why isn't the BotState already an instance of MonadState? How could I fix this?

Abraham P
  • 15,029
  • 13
  • 58
  • 126
  • 2
    Because it isn't looking for `MonadState (SlackState BotState) BotState`, it is looking for `MonadState (SlackState BotState) ((->) BotState)`. Seems like a precedence issue due to `$` and `>>=` - try explicit parenthesization. – user2407038 Aug 08 '16 at 00:31
  • You're right, it was due to the `$` in `uses Slack.userState $ view appState`. Changing that to `uses Slack.userState (view appState)` fixes the problem. I would love to understand what happened there? Why did the `$` not behave as expected in this particular situation? Thanks for your help by the way, if you want to collect the points, write it up as an answer and receive an upvote /accept :) – Abraham P Aug 08 '16 at 07:37
  • Very roughly, one can think of `$` as adding parentheses to the very end of the expression. So, `uses Slack.userState $ stuff1 >>= stuff2` is `uses Slack.userState (stuff1 >>= stuff2)` which was not intended. Note that in a `do` block you wouldn't have this problem: try `do state <- uses Slack.userState $ view appState ; modifying Slack.userState $ set appState $ f state`. You can use a newline instead of `;`, as long as you indent it correctly (`state <-` and `modifying` should start in the same column) – chi Aug 08 '16 at 09:49

1 Answers1

2

$ operator has fixity 0, while >>= has fixity 1, so code like this would work:

main :: IO ()
main = do
  putStrLn "hello world" >>= \_ -> putStrLn "hi"

But not this one:

main :: IO ()
main = do
  putStrLn $ "hello world" >>= \_ -> putStrLn "hi"

It's being interpreted as:

main :: IO ()
main = do
  putStrLn ("hello world" >>= \_ -> putStrLn "hi")

To see fixity info, use ghci's :info command:

 :info $
($) ::
  forall (r :: ghc-prim-0.5.0.0:GHC.Types.RuntimeRep) a (b :: TYPE
                                                                r).
  (a -> b) -> a -> b
    -- Defined in ‘GHC.Base’
infixr 0 $
 :info >>=
class Applicative m => Monad (m :: * -> *) where
  (>>=) :: m a -> (a -> m b) -> m b
  ...
    -- Defined in ‘GHC.Base’
infixl 1 >>=

Also, if you're not sure, good old parentheses are always here for the rescue :)

Konstantine Rybnikov
  • 2,457
  • 1
  • 22
  • 29