Purescript error crib sheet regarding type inference and ‘do’

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 where
import Prelude
import Effect.Console (log)
data Foo a = Bar a | Baz
parse s = do
pure $ Bar s
main = do
let parsed =
case (parse "hi") of
Bar a -> ("Bar " <> a)
Baz -> "Baz"
log parsed
May look ok visually but
Error found:
in module Try
at src/Try.purs line 14, column 32 — line 14, column 33
Could not match type
Foo String
with type
while checking that type t0
is at least as general as type String
while checking that expression a
has type String
in 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.

Warning found:
at src/Try.purs line 8, column 1 — line 9, column 15
No 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 parse
See 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).

Like what you read? Give art yerkes a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.