digitallyinduced / haskell-ux

Let's make Haskells error messages helpful :)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Haskell UX: Let's Make Haskell's Error Messages Helpful

Often GHC Haskell error messages are not helpful or confusing. This is a huge barrier to entry for people just adopting haskell.

Let's collect these "bad" error messages here and suggest and improved error message.

Error Message Design Goals

Good error messages should be clear, actionable, and practical. A great error message tells the user what went wrong and what the user needs to do to fix the problem. It should direct the user to the solution most likely needed.

Inspiration:

Contribute

Please make a PR and add your own suggestions to this file :) It's best to add actual real world error messages from a real world use case.


List of UX Improvements

HUX1: Type level lists with only a single element need a ' in front of the list. Prepend a ' like '["email]' to get it working.

Details:

Given this code:

action CreateUserAction = do
        let user = newRecord @User
        let password = param @Text "password"
        user
            |> set #passwordHash password
            |> fill @["email"]
            |> validateField #email isEmail
            |> validateField #passwordHash nonEmpty
            |> debug
            |> ifValid \case
            Left user ->
                render NewView {..}
            Right user -> do
                hashed <- hashPassword (get #passwordHash user)
                user
                    |> set #passwordHash hashed
                    |> createRecord

                setSuccessMessage "You have successfully registered"

GHC errors with:

Web/Controller/Users.hs:16:23
    * Expected a type, but `"email"' has kind `Symbol'
    * In the type `["email"]'
      In the second argument of `(|>)', namely `fill @["email"]'
      In the first argument of `(|>)', namely
        `user |> set #passwordHash password |> fill @["email"]'
   |
16 |             |> fill @["email"]
   |                       ^^^^^^^

A better error message would be:

Web/Controller/Users.hs:16:23
    * Type level lists with only a single element need a ' in front of the list. Prepend a ' like `'["email]' to get it working.
    * In the type `["email"]'
      In the second argument of `(|>)', namely `fill @["email"]'
      In the first argument of `(|>)', namely
        `user |> set #passwordHash password |> fill @["email"]'
   |
16 |             |> fill @["email"]
   |                       ^^^^^^^

Reported to GHC via https://gitlab.haskell.org/ghc/ghc/-/issues/19096

HUX2: The let-expression is only indented 4 spaces from the do-statement, but it needs to be indented 8 spaces

Details: Given this code:

    action NewEventAction = do
        now <- getCurrentTime
        let event = newRecord @Event
            |> set #createdAt now -- THIS LINE NEEDS MORE INDENTATION
        render NewView { .. }

GHC errors with:

Admin/Controller/Events.hs:26:9: error:
    The last statement in a 'do' block must be an expression
      let event = newRecord @Event
   |
26 |         let event = newRecord @Event
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

A better error message would be:

Admin/Controller/Events.hs:26:9: error:
    The let-expression is only indented 4 spaces from the do-statement, but it needs to be indented 8 spaces
      '|> set #createdAt now'
   |
26 |         let event = newRecord @Event
27 |             |> set #createdAt now
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

Reported to GHC via https://gitlab.haskell.org/ghc/ghc/-/issues/19097

HUX3: The call to `putContext` requires an implicit parameter `?context::ControllerContext` to be available. Change the type signature to this: fetchCategories :: (?modelContext::ModelContext, ?context :: ControllerContext) => IO ()

Details: Given this code:

fetchCategories :: (?modelContext :: ModelContext) => IO ()
fetchCategories = do
  categories :: [Category] <- query @Category |> fetch
  putContext categories

GHC errors with:

Web/FrontController.hs:18:3: error:
    * Could not deduce: ?context::ControllerContext
        arising from a use of `putContext'
      from the context: ?modelContext::ModelContext
        bound by the type signature for:
                   fetchCategories :: (?modelContext::ModelContext) => IO ()
        at Web/FrontController.hs:15:1-59
    * In a stmt of a 'do' block: putContext categories
      In the expression:
        do categories :: [Category] <- query @Category |> fetch
           putContext categories
      In an equation for `fetchCategories':
          fetchCategories
            = do categories :: [Category] <- query @Category |> fetch
                 putContext categories
   |
18 |   putContext categories

A better error message would be:

Web/FrontController.hs:18:3: error:
    * The call to `putContext` requires an implicit parameter `?context::ControllerContext` to be available. Change the type signature to this: fetchCategories :: (?modelContext::ModelContext, ?context :: ControllerContext) => IO ()
        at Web/FrontController.hs:15:1-59
    * In a stmt of a 'do' block: putContext categories
      In the expression:
        do categories :: [Category] <- query @Category |> fetch
           putContext categories
      In an equation for `fetchCategories':
          fetchCategories
            = do categories :: [Category] <- query @Category |> fetch
                 putContext categories
   |
18 |   putContext categories

Reported to GHC via https://gitlab.haskell.org/ghc/ghc/-/issues/19098

HUX4: Perhaps you meant a :: Maybe Int ?

Details: Given this code:

a :: Just Int
a = Just 5

GHC errors with:

Not in scope: type constructor or class 'Just'A data constructor of that name is in scope; did you mean DataKinds?

A better error message would be:

Perhaps you meant a :: Maybe Int?
Not in scope: type constructor or class 'Just'
A data constructor of that name is in scope; did you mean DataKinds?

Suggested on reddit: https://www.reddit.com/r/haskell/comments/kgvdon/improving_haskell_ghc_error_messages/gghjajf/?utm_source=reddit&utm_medium=web2x&context=3

HUX5: Perhaps you want to use `pure`?

Details: Given this code:

initModelContext :: FrameworkConfig -> IO ModelContext
initModelContext FrameworkConfig { environment, dbPoolIdleTime, dbPoolMaxConnections, databaseUrl } = do
    let isDevelopment = environment == Env.Development
    modelContext <- (\modelContext -> modelContext { queryDebuggingEnabled = isDevelopment }) <$> createModelContext dbPoolIdleTime dbPoolMaxConnections databaseUrl
    modelContext

GHC errors with:

IHP/Server.hs:133:5: error:
     Couldn't match expected type IO ModelContext
                  with actual type ModelContext
     In a stmt of a 'do' block: modelContext
      In the expression:
        do let isDevelopment = environment == Env.Development
           modelContext <- (\ modelContext
                              -> modelContext {queryDebuggingEnabled = isDevelopment})
                             <$>
                               createModelContext dbPoolIdleTime dbPoolMaxConnections databaseUrl
           modelContext
      In an equation for initModelContext’:
          initModelContext
            FrameworkConfig {environment, dbPoolIdleTime, dbPoolMaxConnections,
                             databaseUrl}
            = do let isDevelopment = ...
                 modelContext <- (\ modelContext
                                    -> modelContext {queryDebuggingEnabled = isDevelopment})
                                   <$>
                                     createModelContext
                                       dbPoolIdleTime dbPoolMaxConnections databaseUrl
                 modelContext
    |
133 |     modelContext

A better error message would be:

IHP/Server.hs:133:5: error:
     Perhaps you meant `pure modelContext`?
    
    Couldn't match expected type IO ModelContext
                  with actual type ModelContext
     In a stmt of a 'do' block: modelContext
      In the expression:
        do let isDevelopment = environment == Env.Development
           modelContext <- (\ modelContext
                              -> modelContext {queryDebuggingEnabled = isDevelopment})
                             <$>
                               createModelContext dbPoolIdleTime dbPoolMaxConnections databaseUrl
           modelContext
      In an equation for initModelContext’:
          initModelContext
            FrameworkConfig {environment, dbPoolIdleTime, dbPoolMaxConnections,
                             databaseUrl}
            = do let isDevelopment = ...
                 modelContext <- (\ modelContext
                                    -> modelContext {queryDebuggingEnabled = isDevelopment})
                                   <$>
                                     createModelContext
                                       dbPoolIdleTime dbPoolMaxConnections databaseUrl
                 modelContext
    |
133 |     modelContext
HUX6: Type names must start with a capital letter

Details: Given this code:

data continent = Continent
-- or
newtype continent = Continent

GHC errors with:

<interactive>:3:6: error:
    Malformed head of type or class declaration: continent

A better error message would be:

IHP/Server.hs:133:5: error:
     Perhaps you meant `Continent`?
     Type names for `data` and `newtype` must start with a capital letter.
HUX7: Illegal .. notation for constructor LogOutView. The constructor has no labeled field

Details: Given this code:

data LogOutView = LogOutView

instance View LogOutView where
    html LogOutView { .. } = [hsx||]

GHC errors with:

Illegal .. notation for constructor LogOutView. The constructor has no labeled field

A better error message would be:

The 'data LogOutView' has no fields, so you cannot use 'LogOutView { .. }' here. Perhaps remove the '{ .. }' here, or add some fields to 'data LogOutView'? 

Goodies that are being worked on at GHC

Better parsing error

Great work by https://github.com/guibou

PR: https://gitlab.haskell.org/ghc/ghc/-/merge_requests/4711

Was given the following error message when parsed with current GHC:

[1 of 1] Compiling Main             ( ParseErrorTest.hs, ParseErrorTest.o )

ParseErrorTest.hs:6:1: error:
    parse error (possibly incorrect indentation or mismatched brackets)
  |
6 | data Chicken = CotCotCot
  | ^

With this pull request, the error message is as following:

[1 of 1] Compiling Main             ( ParseErrorTest.hs, ParseErrorTest.o )

ParseErrorTest.hs:1:17: error:
    Parse error with context: Parsing error: please close this list.
  |
1 | foo x y z = bar [(x, y, z),
  |                 ^

ParseErrorTest.hs:2:13: error:
    Parse error with context: Parsing error: please close this tuple brace.
  |
2 |             (y, x, z,
  |             ^

ParseErrorTest.hs:8:21: error:
    Parse error with context: Parsing error: please close this brace.
  |
8 | bar a b c d = print ((a, b, c, d) + "hello"
  |                     ^

ParseErrorTest.hs:14:3: error:
    Parse error with context: Parsing error: let/in clause without body. Please add a body.
   |
14 |   in
   |   ^^

About

Let's make Haskells error messages helpful :)

License:MIT License