The following sections are tips for debugging issues that may arise in a strongly-typed language via the compiler.
In the following error...
Could not match type
A
with type
B
... rest of error ...
... one might expect A
to be the "actual" type and B
to be the "expected" type. However, sometimes the two are swapped, so that A
is the "expected" type and B
is the "actual" type. This is not desirable, but is currently how the compiler works.
Why? Because the compiler uses a mixture of unification and type inference to check types. See purescript/purescript#3399 for more information.
(thomashoneyman recommended I document this. These examples might be incorrect since I am not fully aware of the comment that garyb made, but the general idea still applies.)
Recall that { label :: Type }
is syntax sugar for Record (label :: Type)
So, the below error means a Record
could not unify with some other type:
Could not match type
{ label :: Type }
with type
String
Whereas the below error means a Record
was the correct type, but some of its label-type associations were missing.
Could not match type
Record (label :: Type)
with type
Record (label :: Type, forgottenLabel :: OtherType)
Otherwise known as "typed holes."
If you recall in Syntax/Basic Syntax/src/Data-and-Functions/Type-Directed-Search.md
, we can use type-directed search to
- help us determine what an entity's type is
- guide us in how to implement something
- see better ways to code something via type classes
As an example, let's say we have a really complicated function or type
main :: Effect Unit
main = do
a <- computeA
b <- computeB
c <- (\a -> (\c -> doX c) <$> box a) <$> (Box 5) <*> (Box 8)
If we want to know what the type will be for doX
, we can rewrite that entity using a type direction search, ?doX
, and see what the compiler outputs:
main :: Effect Unit
main = do
a <- computeA
b <- computeB
c <- (\a -> (\c -> ?doX c) <$> box a) <$> (Box 5) <*> (Box 8)
If we're not sure what type a function outputs, we can precede the function with our search using ?name $ function
:
main :: Effect Unit
main = do
a <- computeA
b <- computeB
c <- (\a -> (\c -> ?help $ doX c) <$> box a) <$> (Box 5) <*> (Box 8)
If you encounter a problem or need help, this should be one of the first things you use.
This is known as "typed wildcards".
In a function body, wrapping some term with (term :: _)
will cause the compiler to infer the type for you.
main :: Effect Unit
main = do
a <- computeA
b <- computeB
c <- (\w x -> ((doX x) :: _)) <$> box a) <$> (Box 5) <*> (Box 8)
There are two possible situations where this idea might help:
- You know how to write the body for a function but aren't sure what it's type signature is
- You're exploring a new unfamiliar library and are still figuring out how to piece things together. So, you attempt to write the body of a function but aren't sure what it's type signature will be.
In such cases, we can completely omit the type signature and the compiler will usually infer what it is for us:
-- no type signature here for `f`,
-- so the compiler will output a warning
-- stating what its inferred type is
f = (\a -> (\c -> doX c) <$> box a) <$> (Box 5) <*> (Box 8)
However, the above is not always useful when one only wants to know what the type of either an argument or the return type. In such situations, one can use typed wildcards from above in the type signature:
doesX :: String -> _ -> Int
doesX str anotherString = length (concat str anotherString)
Sometimes, I wish we could have a 'unification trace' or a 'type inference trace'. I know the code I wrote works, but there's some mistake somewhere in my code that's making the compiler infer the wrong type at point X, which then produces the type inference problem much later at point Y. To solve Y, I need to fix the problem X, but I'm not sure where X is.
Here's an example:
type Rec = { a :: String }
f :: String -> String
f theString = wrap (unwrap theString)
where
wrap :: String -> Rec
wrap theString = { a: theString }
{-
the mistake! Compiler says
Cannot match type
{ a :: String }
with type
{ a :: String, b :: String }
unwrap :: Rec -> String
unwrap rec = rec.b
From the Slack channel, garyb mentioned passing the --verbose-errors
flag to the compiler. This will output a LOT of information, but it's that or nothing. To do that, run this code:
spago build -- --verbose-errors
spago build -- -v
(This section assumes familiarity with the Design Patterns/Partial Functions/
folder)
Taken from safareli's comment in "When should you use primitive types instead of custom types?"", there might be times where you want to use a partial function to get or compute some value that might not be there. If one just uses unsafePartial $ <unsafeFunction>
, the error message will likely not be helpful:
-- Don't do this.
foo :: forall a. Maybe a -> a
foo mightBeHere =
-- we assume that 'mightBeHere' is the "Just a" constructor
unsafePartial $ fromJust mightBeHere
sarafeli
's suggestion is to pattern match on the value and use unsafeCrashWith
instead to provide a much better error message in case your assumption is proven invalid.
foo :: forall a. Maybe a -> a
foo mightBeHere = case mightBeHere of
Nothing -> unsafeCrashWith "'mightBeHere' should be a valid 'a'"
Just v -> v