bitemyapp / esqueleto

New home of Esqueleto, please file issues so we can get things caught up!
BSD 3-Clause "New" or "Revised" License
372 stars 107 forks source link

"Couldn't match type" on GHC 9.0 when using (.), works with ($) #313

Open pbrisbin opened 2 years ago

pbrisbin commented 2 years ago

This is not a bug, as far as I can tell. But I wanted to make you aware:

{-# LANGUAGE FlexibleContexts #-}

module Repro
    ( main
    ) where

import Prelude

import Control.Monad.IO.Class (MonadIO)
import Database.Esqueleto.Legacy

example
    :: ( MonadIO m
       , BackendCompatible SqlBackend (PersistEntityBackend a)
       , PersistEntity a
       , PersistField (Key a)
       )
    => Key a
    -> SqlPersistT m [Entity a]
example x = select . from $ \xs -> do
    where_ $ xs ^. persistIdField ==. val x
    pure xs

main :: IO ()
main = putStrLn "hi"

On GHC-9.0 (the current LTS), this fails with a very confusing error:

% stack --resolver lts-19.3 runhaskell --package esqueleto -- repro-esq.hs

repro-esq.hs:21:13: error:
    • Couldn't match type: forall backend.
                           SqlBackendCanRead backend =>
                           Control.Monad.Trans.Reader.ReaderT backend m0 [Entity a]
                     with: Control.Monad.Trans.Reader.ReaderT SqlBackend m [Entity a]
      Expected: SqlPersistT m [Entity a]
        Actual: SqlReadT m0 [Entity a]
    • In the expression:
        select . from
          $ \ xs
              -> do where_ $ xs ^. persistIdField ==. val x
                    pure xs
      In an equation for ‘example’:
          example x
            = select . from
                $ \ xs
                    -> do where_ $ xs ^. persistIdField ==. val x
                          ....
    • Relevant bindings include
        x :: Key a (bound at repro-esq.hs:21:9)
        example :: Key a -> SqlPersistT m [Entity a]
          (bound at repro-esq.hs:21:1)
   |
21 | example x = select . from $ \xs -> do
   |

(The SqlPersistT vs SqlReadT is a red-herring, the m0 is the problem.)

On GHC-9.2, you can enable ImpredicativeTypes to fix it. But on GHC 9.0, you have to do this:

- example x = select . from $ \xs -> do
+ example x = select $ from $ \xs -> do

I'm also discussing this phenomena in a GHC Issue.

So, yeah...

parsonsmatt commented 2 years ago

Oh, yeah, thats Simplified Subsumption, here to ruin your expectations for how eta reduction "just works" in Haskell.

Probably going to drop the SqlReadT type in persistent upstream because of it.

mjrussell commented 2 years ago

Im trying to better understand the options available here after running into a similar issue after upgraded to 9.2 and using 3.5.6.1 of esqueleto.

It seems like the GHC issue and @pbrisbin were able to work around this with ImpredicativeTypes but I've got examples in our codebase where that doesnt work when defining a type alias that uses SqlReadT / SqlWriteT. Is there a way to get things working without fully replacing these type aliases?

Maybe just get to 9.2.4 and re-enable DeepSubsumption?

parsonsmatt commented 2 years ago

Yeah, if you jump directly to 9.2.4 and enable DeepSubsumption then you'll be fine