I've got a situation in a Haskell web project where I'm getting the error Ambiguous type variable
.
The relevant code is
--- Other import statements
import qualified Model as Model
---------- HTTP Handlers
needItem db user itemName = do
Model.changeItemStatus db user itemName Model.Need
listItems db user
gotItem db user itemName = do
Model.changeItemStatus db user itemName Model.Got
listItems db user
newItem db user itemName comment count = do
Model.insertItem db user itemName comment count
listItems db user
listItems db user = do
lst <- Model.listItems db user
resOk lst
--- rest of the program
The errors are specifically complaining about insertItem
and changeItemStatus
, both of which are acid-state
query functions
insertItem db name itemName comment count = withAccount db name newItem
where item = Item { itemName = itemName, itemComment = comment, itemCount = count, itemStatus = Need }
newItem account = update' db $ NewItem account item
-- other stuff
changeItemStatus db name itemName status = withAccount db name cItem
where cItem account = withItem account itemName
(\i -> update' db $ ChangeItem account $ i { itemStatus = status})
withAccount
and withItem
are helper functions to help me deal with Maybe
return values from the database. They're defined as
withAccount :: MonadIO m => AcidState GoGetDB -> String -> (Account -> a) -> m (Maybe a)
withAccount db name fn = do
maybeAccount <- query' db $ AccountByName name
return $ do acct <- maybeAccount
return $ fn acct
withItem :: Account -> String -> (Item -> a) -> Maybe a
withItem account itemName fn = do
item <- getOne $ (accountItems account) @= itemName
return $ fn item
Ok, now then.
I've read over the AcidState documentation in the Happstack crash-course several times, but it didn't help much; they use queries directly in their response generating functions which
Ambiguous type variable
error, except pointing at the query'
call iteslf, query'
or update'
would be (their functions are all AcidState DBName -> ServerPart Response
, since they generate the response directly). I've tried to put together type signatures for insertItem
and changeItemStatus
by using :t
on pieces of the expression, but every attempt has given me what I assume is the worse error of No instance for (MonadIO m1)
instead.
I'm not a particularly adept Haskeller yet, so I feel like the only other thing I could try is sprinkling random return $
s around the place, but that doesn't sound like it would have a good chance of actually fixing the problem, or teaching me anything.
The general concept I'm trying to implement is: "Make this change to the DB, and then return the (potentially altered) list of relevant elements for the current user".
Any hints on what I should try next, or where I'm specifically going wrong? Am I thinking about this in entirely the wrong way? Is there other documentation I could consult on the subject?
PS. I've included what I think is all relevant code above, but if you want to see the complete source, it's here and here.
EDIT: Full type errors
/home/inaimathi/projects/goget/goget.hs:31:3:
Ambiguous type variable `m2' in the constraint:
(Control.Monad.IO.Class.MonadIO m2)
arising from a use of `Model.changeItemStatus'
Probable fix: add a type signature that fixes these type variable(s)
In a stmt of a 'do' block:
Model.changeItemStatus db user itemName Model.Need
In the expression:
do { Model.changeItemStatus db user itemName Model.Need;
listItems db user }
In an equation for `needItem':
needItem db user itemName
= do { Model.changeItemStatus db user itemName Model.Need;
listItems db user }
/home/inaimathi/projects/goget/goget.hs:35:3:
Ambiguous type variable `m1' in the constraint:
(Control.Monad.IO.Class.MonadIO m1)
arising from a use of `Model.changeItemStatus'
Probable fix: add a type signature that fixes these type variable(s)
In a stmt of a 'do' block:
Model.changeItemStatus db user itemName Model.Got
In the expression:
do { Model.changeItemStatus db user itemName Model.Got;
listItems db user }
In an equation for `gotItem':
gotItem db user itemName
= do { Model.changeItemStatus db user itemName Model.Got;
listItems db user }
/home/inaimathi/projects/goget/goget.hs:39:3:
Ambiguous type variable `m0' in the constraint:
(Control.Monad.IO.Class.MonadIO m0)
arising from a use of `Model.insertItem'
Probable fix: add a type signature that fixes these type variable(s)
In a stmt of a 'do' block:
Model.insertItem db user itemName comment count
In the expression:
do { Model.insertItem db user itemName comment count;
listItems db user }
In an equation for `newItem':
newItem db user itemName comment count
= do { Model.insertItem db user itemName comment count;
listItems db user }
Write type signatures for your functions to capture what they should do. Write them before you write the implementation, then you can use the type signatures, and the better error messages you get from the compiler if it knows what type you want to have, to check that the implementation does [or at least has a chance to do] what you want.
Let us look at the problem children, and what they actually do:
From acid-state
we use
update' :: (UpdateEvent event, MonadIO m) => AcidState (EventState event) -> event -> m (EventResult event)
for
insertItem db name itemName comment count = withAccount db name newItem
where item = Item { itemName = itemName, itemComment = comment, itemCount = count, itemStatus = Need }
newItem account = update' db $ NewItem account item
Now let's see what sort of type that would produce. From the use of
withAccount :: MonadIO m => AcidState GoGetDB -> String -> (Account -> a) -> m (Maybe a)
we see that the result has type
m (Maybe a)
for some MonadIO m
, where a
is the result type of newItem
. Now,
newItem account = update' db something
so
newItem :: MonadIO mio => Account -> mio (EventResult type_of_something)
and thus the result type of insertItem
would be
m (Maybe (mio (EventResult type_of_something)))
and in
newItem db user itemName comment count = do
Model.insertItem db user itemName comment count
listItems db user
the compiler doesn't know which MonadIO mio
it should use in the second line. Thus the type variable mio
is ambiguous.
Note that specifying the type variable there would still not do what you presumably want. I expect that you actually want to execute the update'
, and not only compute the action that would update the database when executed.
For insertItem
, if it should indeed update the database, withAccount
as it stands is not useful. You could maybe use something like
withAccountM :: MonadIO m => AcidState GoGetDB -> String -> (Account -> m a) -> m (Maybe a)
withAccountM db name fn = do
maybeAccount <- query' db $ AccountByName name
case maybeAccount of
Nothing -> return Nothing
Just acct -> do
result <- fn acct
return $ Just result
(not tested, could still be entirely wrong) to actually execute the update'
.
The problems and probable fixes are similar for changItemStatus
.