Reputation: 3953
From my previous question, I've been trying to work out some monadic code. To start, here is a state machine function I'm using:
import Control.Monad
import Control.Monad.Error
newtype FSM m = FSM { unFSM :: String -> m (String, FSM m) }
fsm f [] = return []
fsm f (r:rs) = do
(xs, f') <- unFSM f r
liftM (xs:) (fsm f' rs)
Now, this compiles fine:
exclaim :: (Monad m) => FSM m
exclaim = FSM exclaim'
exclaim' xs = return (xs ++ "!", exclaim)
But this doesn't, because of the type declaration:
question :: (MonadError String m) => FSM m
question = FSM question'
question' xs
| last xs == '?' = throwError "Already a question"
| otherwise = return (xs ++ "?", question)
The error is Non type-variable argument
, which I think is referring to the String
after MonadError
. If I remove the type declaration, I get Could not deduce
instead. I understand enabling FlexibleContexts just "fixes" this but is there something simpler I could be doing to allow me to throw errors? I'd rather not be enabling all sorts of compiler extensions.
Full code here.
Upvotes: 2
Views: 1029
Reputation: 35089
To elaborate on Daniel's answer, his solution is actually the general solution to avoiding FlexibleContexts
.
Any time you have a constraint like:
(SomeTypeConstructor SomeType) => ...
... where SomeType
is some concrete type that triggers the FlexibleInstances
warning, you can always work around FlexibleContexts
by type classing the operations you want to use on SomeType
, such as:
class IsSomeType t where
get :: t -> SomeType
set :: SomeType -> t -> t
... and then incorporating IsSomeType
into your constraint:
(IsSomeType t, SomeTypeConstructor t) => ...
... and using only the methods in IsSomeType
.
Upvotes: 4
Reputation: 183888
If you absolutely don't want to use FlexibleContexts
or NoMonomorphismRestriction
, you can make question
and question'
a wee bit more general to make it compile without turning on extensions in your module:
question :: (Error e, MonadError e m) => FSM m
question = FSM question'
question' :: (Error e, MonadError e m) => String -> m (String, FSM m)
question' xs
| last xs == '?' = throwError $ strMsg "Already a question"
| otherwise = return (xs ++ "?", question)
Make it throw a general Error
type, by using strMsg
, and specify the type signatures.
I would however still prefer enabling FlexibleContexts
.
Upvotes: 4