haskell-servant / servant

Servat is a Haskell DSL for describing, serving, querying, mocking, documenting web applications and more!
https://docs.servant.dev/
1.83k stars 414 forks source link

Add custom type errors #576

Closed fizruk closed 2 years ago

fizruk commented 8 years ago

We all know servant's type errors are awfully long and hard to read (especially for newbies). Let's change that with new feature of GHC 8 — custom type errors!

I don't have anything concrete yet, rather just clearing my head of this. I don't think this is a very hard task, but probably not newcomer-friendly.

jkarni commented 8 years ago

It would be great. The only thing we have to figure out is how to test this: the only way simple I can think of doing it is with doctest, but we'd need to branch on GHC version. I guess we can CPP documentation?

fizruk commented 8 years ago

IIRC when you CPP documentation, you have to CPP the whole block. You can see this in http-api-data's sources.

However, we could add a separate named haddock chunk with doctests and description of nice errors.

soenkehahn commented 8 years ago

Since doctest is meant to be read as a ghci session I think it's generally acceptable to stick to one version of ghc. Otherwise we would have to provide different documentation for different ghc versions, which sounds unpractical. So I would consider just disabling doctest for all but one ghc version.

In this special case, we should prominently point out that the type error messages reflect what ghc-8 outputs. And that previous versions might yield much different (and much worse) type errors.

jkarni commented 8 years ago

+1. I think a positive feedback loop with regards to better error messages is us showing how much better things are in GHC 8, so that more of our users switch to GHC 8, so that the impact of writing better custom errors becomes larger, &c.

On Thu, Aug 25, 2016 at 06:31:02AM -0700, Sönke Hahn wrote:

Since doctest is meant to be read as a ghci session I think it's generally acceptable to stick to one version of ghc. Otherwise we would have to provide different documentation for different ghc versions, which sounds unpractical. So I would consider just disabling doctest for all but one ghc version.

In this special case, we should prominently point out that the type error messages reflect what ghc-8 outputs. And that previous versions might yield much different (and much worse) type errors.

You are receiving this because you commented. Reply to this email directly or view it on GitHub: https://github.com/haskell-servant/servant/issues/576#issuecomment-242386414

Julian K. Arni Haskell Consultant, Turing Jump https://turingjump.com

arianvp commented 8 years ago

Please paste any horrible error messages here! :)

arianvp commented 8 years ago

https://www.reddit.com/r/haskell/comments/4uaef3/help_with_servantclient071/ is a great example

chkl commented 8 years ago
/home/klingerc/servant/example-servant-custom-monad/src/App.hs:47:10:
    Couldn't match type ‘ReaderT
                           AppConfiguration Data.Functor.Identity.Identity [Issue]
                         :<|> (IssueId
                               -> ReaderT AppConfiguration Data.Functor.Identity.Identity Issue)’
                   with ‘ReaderT
                           AppConfiguration Data.Functor.Identity.Identity [Issue]’
    Expected type: ServerT API MyMonad
      Actual type: ReaderT
                     AppConfiguration Data.Functor.Identity.Identity [Issue]
    In the expression: hListIssues <|> hGetIssue
    In an equation for ‘server’:
        server
          = hListIssues <|> hGetIssue
          where
              hListIssues :: MyMonad [Issue]
              hListIssues = undefined
              hGetIssue :: IssueId -> MyMonad Issue
              hGetIssue = undefined

/home/klingerc/servant/example-servant-custom-monad/src/App.hs:47:26:
    Couldn't match expected type ‘ReaderT
                                    AppConfiguration Data.Functor.Identity.Identity [Issue]’
                with actual type ‘IssueId -> MyMonad Issue’
    Probable cause: ‘hGetIssue’ is applied to too few arguments
    In the second argument of ‘(<|>)’, namely ‘hGetIssue’
    In the expression: hListIssues <|> hGetIssue

after confusing <|> with :<|>

arianvp commented 8 years ago

@chkl hmm I think this one is hard to improve as the mistake is made on the term level and not in the API definition

arianvp commented 8 years ago

https://github.com/haskell-servant/servant/tree/type-errors

rikvdkleij commented 8 years ago
Preprocessing test suite 'spec' for servant-server-0.8.1...
[ 8 of 10] Compiling Servant.ServerSpec ( test/Servant/ServerSpec.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.0.0/build/spec/spec-tmp/Servant/ServerSpec.o )

/home/rik/haskell/libraries/servant/servant-server/test/Servant/ServerSpec.hs:72:5: error:
    • Overlapping instances for Servant.Server.Internal.Context.HasContextEntry
                                  '[AuthHandler Request String, BasicAuthCheck String,
                                    NamedContext "foo" '[]]
                                  (AuthHandler Request (AuthServerData (AuthProtect "foo")))
        arising from a use of ‘serveWithContext’
      Matching instances:
        two instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
      (The choice depends on the instantiation of ‘’
       To pick the first instance above, use IncoherentInstances
       when compiling the other instance declarations)
    • In the expression:
        serveWithContext comprehensiveAPI comprehensiveApiContext
      In a pattern binding:
        _ = serveWithContext comprehensiveAPI comprehensiveApiContext

Because we forgot the following line:

type instance AuthServerData (AuthProtect "foo") = String
jkarni commented 8 years ago

Ouch! Fixing @rikvdkleij 's error would be nice.

fizruk commented 8 years ago

@jkarni we've tried a few things.

First we've tried adding a type instance AuthServerData a = TypeError ..., but that then it conflicts with actual instances. We then tried to wrap AuthServerData type family in a class to be able to use an OVERLAPPABLE instance for any type a, but that didn't help (the conflict remained).

I think there is a hacky way to overcome this... but I have to try it out first.

domenkozar commented 6 years ago

I've found generics help quite a bit with that, depending on where things go wrong. Servant 0.14.1 shipped https://haskell-servant.readthedocs.io/en/stable/cookbook/generic/Generic.html

jkarni commented 2 years ago

I believe we now have some custom type errors (and better errors via NamedRoutes). Closing, but if anyone comes across a bad error message, please open an issue!