Skip to content

Latest commit

 

History

History
72 lines (50 loc) · 3.15 KB

21.markdown

File metadata and controls

72 lines (50 loc) · 3.15 KB

21. Custom Type Errors

Today's post is a guest post by @FrigoEU, who has recently done work to improve support for custom type errors in the PureScript compiler. Custom type errors were inspired by this GHC proposal and can make type-level programming far more enjoyable for your users.


Getting good error messages when the compiler rejects your program is very important for the day to day usability of a language. In an attempt to allow library authors to help out with this and generate better error messages for their users, the ability to define "Fail" instances for typeclasses was added to the compiler in v0.9.1. When a user of a typeclass tries to use one of these instances, the compiler will throw a custom error. Which error depends on how you define your Fail instance. For example:

module Main where

import Prelude
import Control.Monad.Eff.Console (log)

class Serialize a where
  serialize :: a -> String

instance cannotSerializeFunctions 
    :: Fail ("Cannot serialize functions.") 
    => Serialize (a -> b) where
  serialize _ = "unreachable"

main = do
  log (serialize ((+) 1))

Try PureScript link: http://try.purescript.org/?gist=bca55ad0d2a009c2ed5c0c6c5e437c7e

This code will result in the custom error: "Cannot serialize functions". This is nice, but could still be improved. We provided two functions in v0.10.1: TypeString and TypeConcat. These functions are in the "Prim" package, which holds the functions that are always provided by the compiler. The type signatures of TypeString and TypeConcat are:

TypeString :: * -> Symbol
TypeConcat :: Symbol -> Symbol -> Symbol

The Fail typeclass takes a Symbol as argument, and with the above functions we can construct new Symbols based on the types that the compiler figures out. If we update our above program as follows:

instance cannotSerializeFunctions 
    :: Fail ("Cannot serialize the function: " <> TypeString (a -> b) <> ". Functions can't be serialized.") 
    => Serialize (a -> b) where
  serialize _ = "unreachable"

Try PureScript link: http://try.purescript.org/?gist=b2ea85258d3245f5fdca1883640ceb70

The compiler will now throw a more detailed error.

Cannot serialize the function: Int -> Int. Functions can't be serialized.

As more and more type level programming is going on in typeclasses thanks to functional dependencies, these features can really help make your typeclass more usable by others.

A more involved but more practical example can be found here: http://try.purescript.org/?gist=441b45128d24b5964bfff7443417160b

When compiling this example without the Fail instance, we get the following message:

No type class instance was found for

    Main.HasField "myfield"
                  HNil
                  String

With the Fail instance, we get this:

A custom type error occurred while solving type class constraints:

    Missing field "myfield" of type String

Whenever you write a new type class, think about if you can write any Fail instances that can help your users when making mistakes. They'll be grateful!