I don’t deny being a purescript babby.
I often found purescript’s type errors difficult to understand as a beginner. I’m writing this to preserve some mental state now that I’ve spent time thinking about how to explain it:
parse s = do — Uses the monad instance of Maybe automatically, but
-- type inference fails. A type signature is required.
pure $ Just s
(An infinite type was inferred for Maybe t1 …)
Whereas, with a type signature, we’re telling purescript what monad instance to select. It seems like magic if you’re used to C++ where types only flow from arguments to results.
parse :: String -> List (Maybe String)
parse s = do -- We’ve called out an arbitrary monad instance (List)
-- and applied it with ‘pure’ via type inference :-)
pure $ Just s> parse "3" -- Neat but maybe surprising.
((Just "3") : Nil)
Other kinds of errors this can lead to:
module Try whereimport Prelude
import Effect.Console (log)data Foo a = Bar a | Bazparse s = do
pure $ Bar smain = do
let parsed =
case (parse "hi") of
Bar a -> ("Bar " <> a)
Baz -> "Baz"
log parsedMay look ok visually butError found:
in module Tryat src/Try.purs line 14, column 32 — line 14, column 33
Could not match typeFoo Stringwith typeStringwhile checking that type t0
is at least as general as type String
while checking that expression ahas type Stringin value declaration main
where t0 is an unknown type
What the heck? It certainly looks like (“Bar “<> a) is a string. It doesn’t call out what t0 is except for the character range in the file (line 14, cols 32–33). Worse, this error doesn’t appear until we use the function.
It’s because we didn’t type annotate and ignored a strange looking warning.
at src/Try.purs line 8, column 1 — line 9, column 15No type declaration was provided for the top-level declaration of parse.
It is good practice to provide type declarations as a form of documentation.
The inferred type of parse was:
forall t4 t6. Applicative t4 => t6 -> t4 (Foo t6)in value declaration parseSee https://github.com/purescript/documentation/blob/master/errors/MissingTypeDeclaration.md for more information,
or to contribute content related to this warning.
t4 has kind * -> * and names an arbitrary applicative functor to which parse can be applied. We might have initially written a function that returned a Foo String, but added the do later and forgot to change the result properly (by adding an Applicative instance on Foo (or ensuring that it gets imported) and removing the explicit constructor).