About This Series
This is the sixth post in a series of short and sweet blog posts about Elm. The stated goal of this series is to take you from “completely clueless about Elm” to “chief Elm guru”, step by step. If you have missed the previous episodes, you might want to check out the table of contents.
Type Annotations Versus Type Inference
Although Elm is a statically typed language, our examples in the previous posts had no type declarations whatsoever. The reason is that Elm can infer the type of almost any expression. That means that everything is typed, either explicitly by adding a type annotation or implicitly by relying on Elm’s type inference.
Let’s revisit some of the functions from the last episode and add type annotations to them. The type annotations are the line just before the function definition:
import Html multiply : number -> number -> number multiply a b = a * b square : number -> number square a = multiply a a productOfSquares : number -> number -> number productOfSquares a b = multiply (square a) (square b) incrementAll : List number -> List number incrementAll list = List.map (\ n -> n + 1) list incrementAll2 : List number -> List number incrementAll2 = List.map (\ n -> n + 1) main : Html.Html main = let print n = Html.text <| toString n in Html.p  [ print <| multiply 3 5 , Html.br   , print <| square 4 , Html.br   , print <| productOfSquares 2 3 , Html.br   , print <| incrementAll [1, 2, 3] , Html.br   , print <| incrementAll2 [1, 2, 3] ]
(Remark: I refactored the main function a bit from the last episode by extracting the duplicated conversion from number/list into an HTML text element.)
To pick one example,
square : number -> number is the type annotation for the function definition of
square. A type annotation should be written directly in the line above the function definition. It is comprised of the function name, a colon, and the types of all input parameters and the return type, each separated by
There is no distinction between a parameter type and the return type. Coming from other languages, you might expect that the type annotation for
multiply somehow makes it clear that two numbers go in and one number comes out. For example the type signature could read
(number, number) -> number. It doesn't. The separator between the first and second input parameter is
->, just as the separator between the second input parameter and the return type. This is because talking about first and second input parameter is just one way of thinking about
multiply. You could also say that
multiply takes only one parameter and returns a function with the signature
number -> number. Both are equally valid points of view. You can either think of this function as
(number, number) -> number or
number -> (number -> number). The reason is that Elm supports currying and partial function application naturally.
Here is a code example that illustrates this:
multiply : number -> number -> number
multiply a b = a * b
multiplyByFive : number -> number
multiplyByFive = multiply 5
-- The expression (multiply 5) yields a new function with signature
-- (number -> number) by partial appication, that is: the first argument to
-- multiply is provided, but not the second.
main : Html.Html
main = multiplyByFive 3 |> toString |> Html.text