Nearly all modules you’ll write in Elm need to import other modules to do their work; also, all our examples so far had some import statements. In this episode, we take a closer look at the import statement and at the different ways to import modules.
About This Series
This is the eighth 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.
Let’s look at a basic example that just renders some static HTML:
import Html import Html.Attributes main : Html.Html main = Html.div  [ Html.p  [ Html.text "This is the first paragraph" ] , Html.p  [ Html.text "This is another paragraph" ] , Html.hr   , Html.ul  [ Html.li  [ Html.text "some" ] , Html.li  [ Html.text "bullet" ] , Html.li  [ Html.text "points" ] ] , Html.p  [ Html.text "This is the " , Html.span [ Html.Attributes.style [("font-weight", "bold")] ] [ Html.text "closing" ] , Html.text " paragraph." ] ]
This would render the following HTML:
<div> <p>This is the first paragraph</p> <p>This is another paragraph</p> <hr> <ul> <li>some</li> <li>bullet</li> <li>points</li> </ul> <p> This is the <span style="font-weight: bold;">closing</span> paragraph. </p> </div>
As always, go ahead and try stuff out for yourself, for example by copying this into a file named
Imports.elm and check the result with elm-reactor.
This piece of Elm imports two modules,
import Html import Html.Attributes
An import statement basically tells the Elm compiler to load these two modules and to use stuff from them whenever it encounters anything that is prefixed with
Html.Attributes., respectively). Thus, we know that the functions
Html.Attributes.style that we are using in the example code come from said modules.
By the way, where do the imported modules come from? That depends. You can import modules from third party packages that have been installed via
elm-package install. You can also import your own modules from your current project’s source folder (there will be a separate blog post on how to structure your code base with modules later).
In this example, we are importing modules from the package
evancz/elm-html so you would need to install this via
elm-package install --yes evancz/elm-html to follow along. If you already did the examples in episode 3 or episode 4 you can just create a new file (say, Imports.elm) in the same
elm-playground directory and you are good to go. We already installed the elm-html package there.
Coming back to the example code, let’s be honest here: This code looks really bloated with all the redundant
Html.qualifiers. This is where the
exposing keyword comes in.
Open Imports aka Unqualified Imports
The following code example makes use of open imports by using
import exposing, so that the imported identifiers can be used without prefix.
import Html exposing (Html, div, hr, li, p, span, text, ul) import Html.Attributes exposing (style) main : Html main = div  [ p  [ text "This is the first paragraph" ] , p  [ text "This is another paragraph" ] , hr   , ul  [ li  [ text "some" ] , li  [ text "bullet" ] , li  [ text "points" ] ] , p  [ text "This is the " , span [ style [("font-weight", "bold")] ] [ text "closing" ] , text " paragraph." ] ]
The first difference I would like to draw your attention to is in the import statement section:
import Html import Html.Attributes
import Html exposing (Html, div, hr, li, p, span, text, ul) import Html.Attributes exposing (style)
exposing to the
import statement we can specify a list of identifiers that can be used unqualified (that is, without the module name as a prefix) in our module. That’s the reason why we can drop all the
Html. prefixes in the main function.
Html.text becomes just
p, and so on.
Another detail is that this does not only apply to functions but to all identifiers that a modules exports. In particular, it also applies to types. The type annotation in the first example had to be written as
main : Html.Html
This is because the name of the module that we import is
Html and the name of the type that this module exports is also
Html. Thus, the full qualified name of that type is
In the second example we added the name of the type to the list of identifiers to be exposed for unqualified usage – if you look close enough, you can spot the
Html in the
exposing clause for the Html import. With that, the type annotation in the second example can be written as
main : Html
The second example already looks a bit cleaner. However, as soon as you start to use more and more HTML tags (or, functions from the
Html module, that is) you always need to add them to the
exposing part of the import. This can be a bit annoying. Elm has a special import syntax to avoid this.
import Html exposing (..) import Html.Attributes exposing (..) -- main function is the same as in the second example
import Html exposing (..) we tell the Elm compiler to expose every identifier the Html module has to offer and we can use them all in their unqualified form.
Another feature of the import statement is that you can alias the imported module.
Say, you would not want to import unqualified from
Html.Attributes, for whatever reason. You would have to prefix the
styles function call with
Html.Attributes again, as in our first example.
Html.Attributes.styles is pretty long though. What you can do is define an alias for that:
import Html.Attributes as Attr
With this import statement, you can use identifiers from
Html.Attributes by prefixing them with
Attr, like this:
Attr.style [("font-weight", "bold")]
You can also combine aliases and exposing. This might make sense if you only expose a few of the imported identifiers but still want a shorthand notation for the qualified usages.
import Html as H exposing (Html) main : Html main = H.div  [ H.text "whatever" ]
Here, we only expose the type
Html for unqualified usage (thus, we can write the type annotation as
main : Html instead of
main : Html.Html) but alias the module name
H and use this alias to refer to the functions from
Html. (Note: I do not recommend this approach, see below for some recommended best practices regarding imports.)
A small number of modules are imported into every module by default, even if you do not have explicit import statements for them. Here is the set of default imports (for Elm 0.16):
import Basics exposing (..) import Debug import List exposing ( List, (::) ) import Maybe exposing ( Maybe( Just, Nothing ) ) import Result exposing ( Result( Ok, Err ) ) import Signal exposing ( Signal )
This means that, for example, you can use every function from the Basics module, without prefixing it.
There are two things that might need a little explaining with these import statements.
First, the import statement for
( List, (::) ): The module
List has a type named
List that gets exposed. This is pretty straight forward and we have already seen this with the
Html module. But what is this
(::) about that also gets exposed? Function names in Elm usually use alphanumeric characters, but you can also use identifiers that only use non-alphanumeric characters (:, |, <, $, …), you just need to wrap those names in parantheses in the function definition and when importing them. As you might recall from our last episode,
:: is the append operator for lists. It is actually not something special build into the Elm compiler but just a regular function definition on the
List module. We can use the function
(::) everywhere because it is imported and exposed by default. Also, you can always use functions like this without the parantheses as infix operators. That’s the reason why you can write
[1, 2] :: [3, 4] in Elm without importing anything explicitly. You can also define your own infix operators that way.
Second, the exposing clauses for the
Result imports use syntax we have not covered yet. The module
Maybe exports a union type named
Maybe. We did not talk about union types yet. For now, it suffices to say that a union type is just an enumeration of type values. Here is the definition of the
type Maybe a = Just a | Nothing
So a value of type
Maybe can either be
Just a for some other type
The import statement
import Maybe exposing ( Maybe( Just, Nothing ) ) makes the two individual values from the union type (
Nothing) available to our code.
Best Practices for Import Statements
Now that we have completed our short tour of import statements in Elm, it is time to talk about some recommendations regarding code style.
The following are my personal preferences, you are welcome to come up with your own.
- Use qualified imports as much as possible. That is, prefer
import Array exposing (..). This makes your code a bit more verbose, but it is immediately obvious where an identifier comes from.
exposing (..)only for a few selected modules. Actually, I tend to only use it for
Html.Eventsat all, nothing else.
- Expose types that have the same name as their module. That is, for a (fictional) module named
LinkedList, that also exports a type
import LinkedList exposing (LinkedList). This way, you can use the type in type annotations like
someFunction : LinkedListinstead of writing
someFunction : LinkedList.LinkedList.
- Do not use aliases (
import Something as Sth) to shorten module names. In my opinion the savings in characters it is not worth the additional cognitive load, especially when modules are aliased differently all over your code base.
- Do use aliases for multi-segment module names and use the last segment as the alias. Example:
import Some.Thing.MyModule as MyModule.
A Word About Writing HTML in Elm
This episode is about import statements but somehow we also briefly touched the topic of how to produce HTML in a readable fashion. The third example given above (using
import Html exposing (..) is okay-ish, but probably not the best we can do.
If you are used to templating languages, you might not like how that looks in Elm. There is a very good discussion about that on the elm-discuss mailing list. Go read it if you are interested in patterns to do this in a nice way. In particular, this answer seems to get it quite right from my point of view.
Remarks About Pre-0.15 Syntax
Elm is still relatively young and the syntax has changed a few times with the latest versions. The syntax will probably stay much more stable with the 1.0 release. There are still a lot of examples out there using older syntax. With regard to imports, version 0.15 brought some important changes, introducing the
You might stumble over examples using the old syntax without the exposing syntax, like this:
import Html (..) import Html.Attributes (style)
For this, just insert
exposing between the module name and the list of exposed identifiers.
With this we conclude the eighth episode of this blog post series on Elm. Continue with the next episode, where we have a closer look at Elm’s type system.