2
votes

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.

2
FlexibleContexts is a quite harmless extension. No need to be afraid of that. Without the type signature, it also compiles if you disable the monomorphism restriction. - Daniel Fischer
Just a little adverse to extensions if they are not needed. I'd rather look for an alternative approach, restructuring the code for example. Solution 1 is FlexibleContexts. Any other suggestions? - me2
More or less, all the solutions you're going to get are going to be "Turn on FlexibleContexts". Don't be afraid of extensions, they're there to help you. And this one isn't mysterious. It is specifically to allow you to do exactly what you want to do. Nothing more, nothing less. - Carl

2 Answers

4
votes

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.

4
votes

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.