One of Elm’s most important characteristics is its static type system. This enables Elm to make much stronger guarantees during run time compared to dynamic languages like JavaScript. This boils down to “If it compiles, it’ll never throw a runtime exception”. In this episode, we’ll look into the type system and on type annotations in particular more closely.
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 (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:
import Html
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
-- As you probably have guessed, the output of this snippet is 15.
Composing TypesLet's also have a look at the other two functions from the previous episode (and their type annotations):
incrementAll : List number -> List number
incrementAll list = List.map (\ n -> n + 1) list
incrementAll2 : List number -> List number
incrementAll2 = List.map (\ n -> n + 1)
These functions work with lists, but when talking about lists it is also important which type the elements in the list have. Therefore the full type here is not simply List but List number, that is, a list of number elements. This is just an example for the more general concept of parameterized types. This is mostly used in containers (like List or Maybe) and usually defines which type the contained elements have.Generating Type Annotations AutomaticallyYou can even let the Elm compiler tell you the type annotations. If you have Elm code without type annotations and you compile it with elm-make with the --warn flag, it will tell you all type annotations it inferred.Going back to the example from the previous post (which had no type annotations), here's how that looks like:elm-make --warn Functions.elm
=================================== WARNINGS ===================================
-- missing type annotation -------------------------------------- Functions.elm
Top-level value `multiply` does not have a type annotation.
3│ multiply a b = a * b
^^^^^^^^^^^^^^^^^^^^
I inferred the type annotation so you can copy it into your code:
multiply : number -> number -> number
Isn't that nice? I think it is.ConclusionAdding type annotations to your programs or omitting them is a matter of taste and style. However, the Elm style guide recommends having type annotations on all top level definitions. In my experience they often make it easier to solve compiler errors. And, to be quite honest, you'll do a fair share of fighting compiler errors when working with Elm. Therefore it usually pays off to add type annotations to your functions.There is much more to Elm's type system then what have covered today -- union types, type aliases, type variables, tuples and records to name a few – but those will be topics for another Elm Friday.This concludes the sixth episode of this blog post series on Elm. Continue with the next episode, that is all about lists, a central data type in Elm.
More articles
fromBastian Krol
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Bastian Krol
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.