Question

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

  • I've tried and got the same Ambiguous type variable error, except pointing at the query' call iteslf,
  • I don't really want to do since that would force me to mix my model/controller code and
  • doesn't help my particular situation, since it doesn't show me what the concrete return type of a function that calls 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 }
Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top