3

I am trying to use the Beam Haskell library. Consider the following two snippets:

Version 1:

getDb                = open "shoppingcart1.db"
runDebug conn        = runBeamSqliteDebug putStr conn
runDebugInDb actions = do conn <- getDb
                          runDebug conn actions

Version 2:

dbFile               = "shoppingcart1.db"
getDb                = open dbFile
runDebug conn        = runBeamSqliteDebug putStr conn
runDebugInDb actions = do conn <- getDb
                          runDebug conn actions

I would expect that the type inference would be the same. But the type inferred for runDebugInDb is different, even when the inferred type for runDebug and getDb are the same.

ghci for version 1:

λ> :t (getDb, runDebug, runDebugInDb)
(getDb, runDebug, runDebugInDb)
  :: (IO Connection, Connection -> SqliteM a -> IO a,
      SqliteM b -> IO b)

ghci for version 2:

λ> :t (getDb, runDebug, runDebugInDb)
(getDb, runDebug, runDebugInDb)
  :: (IO Connection, Connection -> SqliteM a -> IO a,
      SqliteM () -> IO ())

The correct behavior is the first snippet, so that the query can return the result of the query. To get the second snippet to work, I have to explicitly annotate the type of runDebugInDb.

Being new to Haskell, I don't understand well how the type inference system works. Why does the runDebugInDb type get inferred differently in these two?


Update 1: A minimal example can be found here. This also shows the enabled extensions, which were from the Beam tutorial.

For some reason the behavior is a little bit different: the types from ghci are:

running version 1:

λ> :t (getDb, runDebug, runDebugInDb)
(getDb, runDebug, runDebugInDb)
  :: (IO Connection, Connection -> SqliteM a -> IO a,
      SqliteM b -> IO b)

running version 2:

λ> :t (getDb, runDebug, runDebugInDb)
(getDb, runDebug, runDebugInDb)
  :: (IO Connection, Connection -> SqliteM a -> IO a,
      SqliteM ghc-prim-0.6.1:GHC.Types.Any
      -> IO ghc-prim-0.6.1:GHC.Types.Any)

Update 2: Turning off the ExtendedDefaultRules extension allowed this to work. Enabling the MonomorphismRestriction extension did not appear to change anything.

It seems that ExtendedDefaultRules changes the interpretation of literals. If I understand correctly, the "shoppingcart1.db" string literal is carried along to runDebug and is interpreted differently in that function. Is this a correct interpretation, and how can I be sure about when this extension makes a difference?


Update 3: It seems that ExtendedDefaultRules didn't actually change the behavior. It turns out the behavior is different if I load the entire file into ghci as opposed to copying the lines into ghci and running them one-by-one. The behavior is what I originally reported if I load the entire file (:l TypeInferenceExample.hs) but the type inference is the same if the lines are copied into ghci.

(I thought I was unsetting the ExtendedDefaultRules extension in ghci but actually wasn't.)

amalloy
  • 89,153
  • 8
  • 140
  • 205
Jonathan Lam
  • 16,831
  • 17
  • 68
  • 94
  • 1
    version 1 doesn't define `dbFile`, so I'm not sure what you are displaying in the `:t` statement. – Bob Dalgleish Sep 24 '21 at 18:23
  • @BobDalgleish Sorry, I meant to check the type of `runDebug`, not `dbFile` as was mentioned in the question text. Fixed now – Jonathan Lam Sep 24 '21 at 18:32
  • 1
    Perhaps you should provide whole .hs files so that other can reproduce your findings. Also note that inference at the GHCi prompt is a bit different than inference inside .hs files. You might want to turn off `ExtendedDefaultRules` and maybe turn on `MonomorphismRestriction` at the GHCi prompt so to make inference the same. https://downloads.haskell.org/ghc/latest/docs/html/users_guide/ghci.html?highlight=extendeddefaultrules#extension-ExtendedDefaultRules – chi Sep 24 '21 at 18:34
  • @chi It seems that those extensions don't make a difference in the output, but if I copy and paste the lines into ghci and run them directly (rather than loading the entire file), then the type inference is the same. The question has been updated. – Jonathan Lam Sep 24 '21 at 19:28
  • Even though type inference "works the same" in the ghci prompt as it does in modules, there is a fundamental difference in practice. In a module, GHC can see the whole module at once. So the way that bindings are *used* can give additional information to the type inferred for the binding. But at the prompt the compiler has to consider each entry as you enter it, and generally a variable binding occurs alone, with no usages to guide type inference. This can lead to ambiguity, bindings that are too polymorphic, or it can lead to the "wrong" type being chosen to resolve the ambiguity. – Ben Sep 25 '21 at 11:15
  • @Ben Your logic makes sense but I am still not totally convinced. I agree that it could use the extra information to infer ambiguous types, but `Sqlite a -> IO a` is not any more ambiguous or incorrect than `Sqlite () -> IO ()`. Since the .hs file is loaded into ghci rather than compiled directly, you might also assume that its inferred type wouldn't be narrowed to only cover the usages in the .hs file, but the broadest non-ambiguous type to cover what the user may enter interactively. – Jonathan Lam Sep 25 '21 at 16:15
  • @Ben Additionally, in my original file (not the minimal example) I had uses that required `Sqlite a -> IO a`. These were in separate functions, however, so I'm not sure if that is enough to be "additional information" when inferring the type of `runDebugInDb`. – Jonathan Lam Sep 25 '21 at 16:16
  • @JonathanLam If you have other functions in the same file that use `runDebugInDb`, then yes, GHC can and will use information from those usage sites to infer the type for `runDebugInDb`. There really is no guarantee that taking a function out of its context in a module and pasting it into GHCi will infer the same type as it has in the module. And no, chi's suggestion of turning on `MonomorphismRestriction` and `NoExtendedDefaultRules` still doesn't guarantee it. You are much more likely to get the same type if you explicit type signatures for all of your top-level bindings, though. – Ben Sep 26 '21 at 00:06
  • 1
    @JonathanLam However, I've had a chance to play with your code, and it does seem like something weird is going on. I can't get your reported inferred types at the ghci prompt, but using your linked file I see it *unless* I add `NoMonomorphismRestriction` to the extensions at the top; then it goes away. But the monomorphism restriction isn't supposed to apply to `runDebugInDb`, so I don't know why it's triggering here. It *might* be a GHC bug? (what version of GHC are you using? I replicated with 8.10.4) I couldn't find mention of a known bug that would cause this. – Ben Sep 26 '21 at 00:09
  • 1
    The only other possibly noteworthy thing: the list of extensions you have enabled is significantly longer than the one in the Beam tutorial. In particular it includes `ImpredicativeTypes`, which has been in GHC in a known-broken state for a long time. It supposedly works now in GHC 9.2 (which is only a release candidate at the moment), but does change type inference. I can easily imagine a bug in the new OR old impredicativity support causing a type inference bug, so it may be related (didn't seem to be for me though). Don't use it unless you know you need it (and not at all prior to 9.2). – Ben Sep 26 '21 at 00:18
  • 1
    @Ben The listed extensions all come up at some point in the Beam tutorial. `ImpredicativeTypes` appears later in the tutorial (in part 2 of the tutorial due to lenses). I am running GHC 8.10.7. I think I'll have a good long look at what the extensions do, as I really have no idea what any of them do at this point. (I suppose the lenses are mostly syntactic sugar and I could use the regular selectors instead.) – Jonathan Lam Sep 26 '21 at 01:49
  • 1
    @jonathanlam Oh, fair enough then. Well, of those extensions, `ImpredicativeTypes` is the only one I'd call questionable to have on all the time. The rest are pretty harmless (in that they don't do anything controversial and/or don't affect anything without it being fairly obvious that you're using them). It's worth knowing what the extensions do, but it can be a bit of a slog trying to learn a lot of them at once! – Ben Sep 26 '21 at 02:41

0 Answers0