Understanding Elm's Type Mismatch Error

Hawley Brett

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!