me2
me2

Reputation: 3953

Haskell "Could not deduce", "Non type-variable argument"

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

Answers (2)

Gabriella Gonzalez
Gabriella Gonzalez

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

Daniel Fischer
Daniel Fischer

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

Related Questions