A big reason people love Elm is the compiler’s helpful error messages. The compiler not only points out what’s wrong, but also includes hints for how to fix it.
One of the first error messages you’ll run into is the “Type Mismatch” error. This error is fundamental to Elm’s enforcement of its strict type system and tells you when a function is expecting a different type of argument than the one it has been passed.
In a basic case, this error is straightforward, so first we’ll wrap our heads around what this error is fundamentally about. But in more complex cases, like when you’re passing functions as arguments to other functions, this error can be confusing if you don’t know what to look for.
Type Mismatch Basics
You’ll see the “Type Mismatch” error when, for example, you pass a String
instead of an Int
to a function expecting an Int
.
Given this function:
showAge : Int -> String
showAge age =
String.fromInt age
If you called showAge
with a string, like so:
showAge “invalid argument”
You’d get the Type Mismatch error:
TYPE MISMATCH - The 1st argument to `showAge` is not what I expect:
Html.text (showAge "invalid argument")
#^^^^^^^^^^^^^^^^^^#
This argument is a string of type:
#String#
But `showAge` needs the 1st argument to be:
#Int#
This makes sense – an Int
is not a String
, so the compiler is saying the
wrong type is being passed to the function showAge
. The compiler will not
allow you to pass the wrong types of arguments to a function. We can’t use a
String
in place of an Int
, and so the compiler lets us know that we’re using
the wrong type. This error is fundamental to enforcing that arguments passed to
a function are of the correct type.
Complex Type Mismatch Cases
While this error is usually clear, you might run into circumstances where it’s more confusing. This often happens when you are passing functions to other functions. In most Elm code and functional code more broadly, you’ll see functions being passed to other functions quite commonly. This results in a chain of functions that each expect certain types of arguments, and if one argument in the chain is wrong, you’ll get the Type Mismatch error. But it might be hard to see where things are going awry.
Consider this code, where we want to pass a name
and an age
to the
profileDetails
function, and then we’ll pass the profileDetails
function to
the div
function.
If we pass only the first argument, name
, to profileDetails
, leaving out
age
, like so:
userProfile : User -> Html msg
userProfile user =
div [] [ profileDetails user.name ]
profileDetails : String -> Int -> Html msg
profileDetails name age =
div [] [ text name, text <| String.fromInt age ]
We get the Type Mismatch Error:
TYPE MISMATCH - The 2nd argument to `div` is not what I expect:
2| div [] [ profileDetails user.name ]
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^#
This argument is a list of type:
List #(Int -> Html msg)#
But `div` needs the 2nd argument to be:
List #(Html msg)#
#Hint#: I always figure out the argument types from left to right. If an
argument is acceptable, I assume it is “correct” and move on. So the problem may
actually be in one of the previous arguments!
Why do we get this error?
The first thing we’d probably look at, given the text of the error message, is
the type annotation of profileDetails
. If div
expects a List (Html msg)
,
we’d double check what profileDetails
is doing. So we look at its type
annonation, and we see – well, profileDetails
results in an Html msg
, and
it’s already in a List
when it’s passed to the div
function. Isn’t that
exactly what div
expects?
We’d then look at the hint, which reminds us that the compiler looks at the
arguments left to right, so the first argument to div
here, an empty list,
could also be wrong. But we’re confident that this empty list is an acceptable
first argument here.
The real problem is that profileDetails
is simply missing the second argument
that it expects! profileDetails
expects the user’s name, a String
, and their
age, an Int
. So far we’ve only passed profileDetails
the name
. All we need
to do is pass the age
to profileDetails
as well.
This now compiles:
userProfile : User -> Html msg
userProfile user =
div [] [ profileDetails user.name user.age ]
profileDetails : String -> Int -> Html msg
profileDetails name age =
div [] [ text name, text <| String.fromInt age ]
Why then did the compiler not say, “The arguments to profileDetails
are not
what I expect : profileDetails
expects a String
and an Int
but was only
given a String
”? It might seem like the real problem was in the arguments
passed to profileDetails
, not the ones passed to div
. But the compiler
accepts (profileDetails name)
as valid in itself, just not as the second
argument to div
when in a List. And if the compiler complains about passing
too many arguments to a function, why does it not complain about too few?
The answer has to do with how Elm works, and a concept called “Partial
Application”. When the name
is passed to profileDetails
but not the age
,
we’d say that profileDetails
has been “partially applied”. In Elm, even though
we’d generally say that profileDetails
is a function, implying that it is a
single function, it is in fact a set of functions. The first function takes the
name
and returns a function that takes the age
, and that function finally
returns an Html msg
. So really it’s two functions. If profileDetails
also
took a “Bio”, then it would be three functions under the hood, each one passing
its resulting function to the next, until it produces the final data structure
as represented in the annotation. This process is called “currying”, and it’s
fundamental to how Elm works.
Rule of Thumb
If you see a function in a Type Mismatch error, you’ll want to check that the
function has been passed all its arguments (or the number you’re expecting to
pass it at that point, since you can use partial application deliberately too).
In the above error, where it says the type is: List #(Int -> Html msg)#
, we
can tell that (Int -> Html msg)
represents a function because of how it’s
annotated with the arrow, just like we annotate functions normally. You can
think of it like the function is waiting for an argument, in this case the
age
, an Int
. Here the function is also in a List
because we’ve put it in
the second list to passed div
, div [] [ profileDetails user.name ]
. When you
see a function in the error message, you can likely skip the other debugging
steps and go straight to checking if the function has been partially applied
when you didn’t mean it to be.
While a full examination of currying and partial application is outside the scope of this post, what’s valuable to remember is that each argument to a function in truth represents a distinct function with that argument applied. It therefore must be legal for a function to accept fewer arguments than its total, because that’s what’s already happening each time you pass an argument to a function in Elm. If you come to expect that a function may be passed fewer arguments than its total, when you see a function annotation in the Type Mismatch error you’ll know that this is something to look out for. You might even find yourself using partially applied functions on purpose!