Overview

Elm Friday: Imports (Part VIII)

No Comments

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.

  1. Use qualified imports as much as possible. That is, prefer import Array over import Array exposing (..). This makes your code a bit more verbose, but it is immediately obvious where an identifier comes from.
  2. Use exposing (..) only for a few selected modules. Actually, I tend to only use it for Html, Html.Attributes and Html.Events at all, nothing else.
  3. Expose types that have the same name as their module. That is, for a (fictional) module named LinkedList, that also exports a type LinkedList, use import LinkedList exposing (LinkedList). This way, you can use the type in type annotations like someFunction : LinkedList instead of writing someFunction : LinkedList.LinkedList.
  4. 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.
  5. 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.

Comment

Your email address will not be published. Required fields are marked *