Haskell: GHC cannot deduce type. Rigid type variab

2019-06-23 23:30发布

问题:

I'v seen a couple of posts with a similar subject but they don't really help me to solve my problem. So I dare to repeat.

Now I have a functions with signature:

run' :: Expr query => RethinkDBHandle -> query -> IO [JSON]

this is a database query run function.

I wrap this function in a pool (pool is already created and irrelevant to the question) to simplify connections.

rdb q = withResource pool (\h -> run' (use h $ db "test") q)

Essentially, this function has exact the same signature as the run above.

The problem is that if I use the function without a signature then all is good and GHC is happy figuring things out. As soon as I specify the signature it stops working on certain input complaining about not being able to deduce the type.

There are mainly two input types that are used as query input.

ReQL and Table

Both of those types are instances of Expr so they both accepted by GHC.

As soon as I put the signature everything stops working and GHC coplains about not being able to deduce the type and gives me "rigid type variable bound by the type signature" error. If I make signature more specific like ReQL instead of Expr a, then obveously it stops accepting Table input and visa versa. Specifying input as Expr a, which both ReQL and Table are instances of, stops with the error above. Dropping the signature all together works fine.

So how do I solve this? Dropping the signature feels wrong.

I don't know if I should make the question more generic or more specific but if it helps this is the library with all the types and instances to help with an advice.

Rethink DB

UPDATE

As requested, this is the full code listing producing the error.

main = do
  pool <- createPool (connect "localhost" 28015 Nothing) close 1 300 5
  let rdb q = withResource pool (\h -> run' (use h $ db "test") q)
  scotty 3000 $ basal rdb

basal :: Expr q => (q -> IO [JSON]) -> ScottyM ()
basal r = get "/json" $ showJson r

showJson :: Expr q => (q -> IO [JSON]) -> ActionM ()
showJson r = do
  j <- lift $ r $ table "mytable"
  text $ T.pack $ show j

And this is the full error listing

Main.hs:19:17:
    No instance for (Expr q0) arising from a use of `basal'
    The type variable `q0' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    Note: there are several potential instances:
      instance Expr () -- Defined in `Database.RethinkDB.ReQL'
      instance (Expr a, Expr b) => Expr (a, b)
        -- Defined in `Database.RethinkDB.ReQL'
      instance (Expr a, Expr b, Expr c) => Expr (a, b, c)
        -- Defined in `Database.RethinkDB.ReQL'
      ...plus 24 others
    In the second argument of `($)', namely `basal rdb'
    In a stmt of a 'do' block: scotty 3000 $ basal rdb
    In the expression:
      do { pool <- createPool
                     (connect "localhost" 28015 Nothing) close 1 300 5;
           let rdb q = withResource pool (\ h -> ...);
           scotty 3000 $ basal rdb }

Main.hs:26:19:
    Could not deduce (q ~ Table)
    from the context (Expr q)
      bound by the type signature for
                 showJson :: Expr q => (q -> IO [JSON]) -> ActionM ()
      at Main.hs:24:13-52
      `q' is a rigid type variable bound by
          the type signature for
            showJson :: Expr q => (q -> IO [JSON]) -> ActionM ()
          at Main.hs:24:13
    In the return type of a call of `table'
    In the second argument of `($)', namely `table "mytable"'
    In the second argument of `($)', namely `r $ table "mytable"'

Thank you

回答1:

Reading the error messages it seems the first problem is that the type you specify for showJson is wrong.

As r is being applied directly to table which is table :: String -> Table its type is not

r :: Expr q => q -> IO [JSON]

but instead either

r :: Table -> IO [JSON]

or (using RankNTypes)

r :: forall q . Expr q => q -> IO [JSON]

The first is simpler and more direct while the second is probably closer to your intended meaning---it can be read as "fromJson takes an input which it demands uses only the Expr interface on its argument" instead of "fromJson takes any kind of input which happens to use an Expr instantiated type as its argument". For instance, with the type you've given

fromJson (undefined :: Query -> IO [JSON])

would unify as well... but clearly that's now how r is being used in the function body.

(In particular, it has to do with the positivity of the parameter q. Due to the way this function is written, q acts more like an output argument than an input argument. Indeed, the function creates a Table (with table) instead of demanding one. The argument you've written thus implies that we have a function Expr q => Table -> q.)

Now, this specificity of type transmits upward as well causing basal to have the type

basal :: (Table -> IO [JSON]) -> ScottyM ()

or

basal :: (forall q . Expr q => q -> IO [JSON]) -> ScottyM ()

and thus leading to your Cannot deduce (q ~ Table) error.


At this point I can't be sure why stating an explicit type for rdb would lead to issues, but it may be that clearing this one will stop problems from occurring there. Usually once you've already broken the type system it's very hard to predict its behavior in other locations.