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 .
Qualified Imports
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, Html
and Html.Attributes
:
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.
(or Html.Attributes.
, respectively). Thus, we know that the functions Html.text
or 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
versus
import Html exposing (Html, div, hr, li, p, span, text, ul)
import Html.Attributes exposing (style)
By appending 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 text
, Html.p
becomes 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 Html.Html
.
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
Expose Everything
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
With 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.
Import Aliases
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 Html
as 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.)
Default 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
exposes ( 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 Maybe
and 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 Maybe
type:
type Maybe a = Just a | Nothing
So a value of type Maybe
can either be Nothing
or Just a
for some other type a
.
The import statement import Maybe exposing ( Maybe( Just, Nothing ) )
makes the two individual values from the union type (Just
and 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
overimport Array exposing (..)
. This makes your code a bit more verbose, but it is immediately obvious where an identifier comes from. - Use
exposing (..)
only for a few selected modules. Actually, I tend to only use it forHtml
,Html.Attributes
andHtml.Events
at 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 typeLinkedList
, useimport LinkedList exposing (LinkedList)
. This way, you can use the type in type annotations likesomeFunction : LinkedList
instead of writingsomeFunction : 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.
Last but not least, the modules elm-html-shorthand and elm-bootstrap-html might come in handy for this.
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 exposing
keyword.
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.
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.