4.7

Easier Code Organization

A software program is like an organism. When it’s first brought into the digital world, it’s tiny. It might contain a few functions that solve a problem whose scope is very narrow. It then starts to grow, one function at a time. Before we know it, it turns into a behemoth capable of tackling problems with monstrous complexity.

It’s often easier to read and change small programs. But as programs grow into thousands of lines of code, they become hard to understand and maintain without a good organizational structure. Some of the features in Elm we explored earlier, such as immutability, pure functions, tests, and a powerful type system help us write robust programs, but they don’t enable us to organize our code in a maintainable way. Elm provides three more features that are designed specifically for better code organization: modules, packages, and the Elm Architecture. The Elm Architecture will be covered in chapter 5. We will cover the other two features in this section.

A value is the most basic concept in Elm. 1, a, "Hannibal", [ 1, 2, 3 ] are all values. An expression allows us to compute a value by grouping together other values, operators, and constants. 3 * x + 5 * y + 10 is an expression. We can even use if, case, and let to combine multiple expressions and execute them only when certain conditions are met. As we write more expressions, we’ll inevitably want to reuse some of them in multiple places. We can achieve reusability by using functions to encapsulate a bunch of expressions and give names to the collective task those expressions accomplish.

As our program grows, our data structures also tend to become more complex. To be able to easily describe complex data structures, we need to first define relationships between different kinds of values used in our program through the use of types. As the number of functions and type definitions grow, we need to start grouping together the ones that perform similar tasks into modules. A module is essentially a collection of functions, constants, type definitions, and other values that can be reused in multiple contexts. For example, all functions that perform some kind of operation on a list of values are grouped into a module called List. We might even want to combine multiple modules that solve similar problems into a package. For example, various modules that provide functionalities for writing and manipulating HTML code are grouped together in a package called Html. We can share a package with other programmers by publishing it through the online catalog.

So far, in this book, we’ve created a few modules without understanding how they actually work. In this section, we’ll attempt to understand the syntax for creating new modules and how they get imported into other modules. Along the way, we’ll learn some best practices that will make the code in our modules more maintainable.

Creating a Module

In the Recursive Types section, we created our own data structure called MyList that looked very much like the built-in type List.

type MyList a
    = Empty
    | Node a (MyList a)

Unfortunately, we can’t use any of the functions defined in the List module with MyList, because all those functions expect a list of type List. Let’s rewrite a function from the List module so that it also works on MyList. The source code for Elm’s standard library is freely available. We can read it to figure out how functions in the List module are implemented. Here is the original implementation for List.isEmpty function:

isEmpty : List a -> Bool
isEmpty xs =
    case xs of
        [] ->
            True

        _ ->
            False

You can find the source code for all modules included in the Core package here.

Quite simple, right? If the list is empty it returns True, otherwise, it returns False. Let’s reimplement isEmpty so that it works with MyList too. Before we do that though, we need to create a module first. Create a file named MyList.elm in the beginning-elm/elm-examples directory.

Add the following code to the MyList.elm file.

module MyList exposing (MyList(..), sum, isEmpty)


type MyList a
    = Empty
    | Node a (MyList a)


sum : MyList Int -> Int
sum myList =
    case myList of
        Empty ->
            0

        Node intValue remainingNodes ->
            intValue + sum remainingNodes


isEmpty : MyList a -> Bool
isEmpty xs =
    case xs of
        Empty ->
            True

        _ ->
            False

We created a module called MyList that contains the definition for the MyList type and two functions: sum and isEmpty. In the Recursive Types section, we had already defined MyList and sum in Playground.elm. Now that we have a separate module that will contain all code related to the MyList type, it makes sense to move them over to MyList.elm. Don’t forget to remove the definitions for the MyList type and sum function from Playground.elm.

You already know how MyList and sum work. If not, you can refresh your memory by reading the Recursive Types section. isEmpty is a re-implementation of the List.isEmpty function. The only difference between our implementation and Elm’s is that we use Empty to represent emptiness whereas Elm uses [].

It’s perfectly OK to use the same name for a module and a type. In fact you will see this pattern over and over again with many of the modules Elm provides such as Array, Html, and Task. However, Elm doesn’t allow us to have two modules or types with the same name.

MyList, sum, and isEmpty aren’t accessible outside the module they’re defined in, unless we expose them. Exposing a function is straightforward. We include its name between parentheses after the keyword exposing. But exposing a type requires us to append (..) to the type name. By adding (..), we’re asking Elm to expose the Empty and Node data constructors as well.

Importing a Module

To be able to use the new isEmpty function outside of MyList module, we need to import the module that contains it. Let’s import the MyList module in Playground.elm right below the line that imports Bitwise.

module Playground exposing (..)
.
.
import Bitwise
import MyList

We can verify that the MyList module was imported successfully by loading the Playground module up on a browser. Run elm-reactor from the beginning-elm directory in the terminal if it isn’t running already, and go to http://localhost:8000/elm-examples/Playground.elm on your browser. If you don’t see any errors, that means the module was imported successfully. But if you see the following error make sure that elm-examples is added to the list of source-directories in beginning-elm/elm-package.json.

{
    .
    .
    "source-directories": [
        ".",
        "elm-examples"
    ],
    .
    .
}

We’re now ready to use the isEmpty function in the Playground module. Add the following two constants right above main in Playground.elm.

list1 : MyList.MyList a
list1 =
    MyList.Empty


list2 : MyList.MyList number
list2 =
    MyList.Node 9 MyList.Empty


main =
    ...

Apply the isEmpty function in main to check if the list referenced by list1 is empty or not.

main : Html.Html msg
main =
    MyList.isEmpty list1
        |> toString
        |> Html.text

If you refresh the page at http://localhost:8000/elm-examples/Playground.elm, you should see “True.” Let’s apply isEmpty to list2 to make sure that the false condition is working too.

main : Html.Html msg
main =
    MyList.isEmpty list2
        |> toString
        |> Html.text

If you refresh the page at http://localhost:8000/elm-examples/Playground.elm, you should see “False.” The way we are creating lists now is bit more verbose compared to how we created them in the Recursive Types section.

-- Before
Node 9 Empty

-- Now
MyList.Node 9 MyList.Empty

The reason why we need to prefix Node and Empty with MyList is because MyList type is now being used inside a code file instead of the repl. Before, when we imported the Playground module into the repl, we used (..) after the exposing keyword so that everything inside that module is exposed.

> import Playground exposing (..)

This allowed us to drop the MyList type prefix and use the data constructors directly. To drop the prefix in Playground.elm too, we need to explicitly expose the type when we import a module. Add exposing (MyList(..)) at the end of the line that imports MyList in Playground.elm.

module Playground exposing (..)
.
.
import Bitwise
import MyList exposing (MyList(..))

Notice the syntax for exposing a type — or any other value for that matter — in a module that uses that type is exactly the same as the one used in the module that created the type.

module MyList exposing (MyList(..), sum, isEmpty)


type MyList a
    = Empty
    | Node a (MyList a)
.
.

Now we can get rid of the prefix in Playground.elm.

.
.
list1 : MyList a
list1 =
    Empty


list2 : MyList number
list2 =
    Node 9 Empty


main : Html.Html msg
main =
    MyList.isEmpty list2
        |> toString
        |> Html.text

If you refresh the page at http://localhost:8000/elm-examples/Playground.elm, you should still see “False”. We were also able to remove the MyList module prefix from type annotations now that we’ve explicitly exposed the type.

-- Before
list1 : MyList.MyList a

-- Now
list1 : MyList a

What about MyList.isEmpty inside main? Can we get rid of the prefix there too? Sure. Just add isEmpty to the list of values being exposed in Playground.elm.

module Playground exposing (..)
.
.
import MyList exposing (MyList(..), isEmpty)
.
.

list1 : MyList a
list1 =
    Empty


list2 : MyList number
list2 =
    Node 9 Empty


main : Html.Html msg
main =
    isEmpty list2
        |> toString
        |> Html.text

The only thing that’s not exposed to the Playground module right now is the sum function. Let’s expose that too. Add sum to the list of things being exposed in Playground.elm.

import MyList exposing (MyList(..), sum, isEmpty)

At this point, we have exposed all values from the MyList module. Rather than exposing each value individually, we can use the shorthand (..) for exposing everything in a module.

module Playground exposing (..)
.
.
import MyList exposing (..)

You’ve seen (..) in various places throughout the book. Now you know what it means. This syntax also works on a file where the module is defined.

module MyList exposing (..)
.
.

But, you don’t need to replace everything after exposing in MyList module’s definition with (..) at this time. While we are at it, we can also get rid of the Html prefix by exposing the text function and Html type in Playground.elm like this:

module Playground exposing (..)


import Html exposing (Html, text)
.
.

main : Html msg
main =
    isEmpty list2
        |> toString
        |> text

toString is included in the Basics module which is automatically imported in every file by Elm. Therefore, we can use it without a prefix even if we didn’t explicitly import the Basics module in Playground.elm.

Qualified vs Unqualified Import

What happens if we also want to use the isEmpty function from the List module inside Playground.elm? Let’s find out. Import the List module and add a constant named list3 right above main. After that apply isEmpty to list3 in main like this:

module Playground exposing (..)
.
.
import MyList exposing (..)
import List exposing (isEmpty)
.
.

list1 : MyList a
list1 =
    Empty


list2 : MyList number
list2 =
    Node 9 Empty


list3 : List a
list3 =
    []


main : Html msg
main =
    isEmpty list3
        |> toString
        |> text

If you refresh the page at http://localhost:8000/elm-examples/Playground.elm, you’ll see the following error.

Elm is confused. It doesn’t know which isEmpty to use because we exposed it from both the List and MyList modules. This is a drawback of exposing values when importing a module. To resolve this issue, we need to be explicit about which isEmpty we intend to use. Prefix isEmpty with List in main.

main : Html msg
main =
    List.isEmpty list3
        |> toString
        |> text

Refresh the page at http://localhost:8000/elm-examples/Playground.elm. The error should be gone now.

Qualified Import
When we import a module without exposing anything inside it, it’s called a qualified import. That means we have to prefix all functions, types, constants, and other values from that module with the module name.
-- Qualified Imports

import Html
import MyList


list2 : MyList.MyList number
list2 =
    MyList.Node 9 MyList.Empty


main : Html.Html msg
main =
    MyList.isEmpty list2
        |> toString
        |> Html.text
Unqualified Import
When we explicitly expose values while importing a module it’s called an unqualified import, which means we don’t need to prefix the module name.
-- Unqualified Imports

import Html exposing (Html, text)
import MyList exposing (..)


list2 : MyList number
list2 =
    Node 9 Empty


main : Html msg
main =
    isEmpty list2
        |> toString
        |> text

Writing code in the unqualified style is concise, but it’s also dangerous as we saw above with the name clash. The question is, when should we use qualified over unqualified? It’s best practice to write code in the qualified style by default. Not only because it gets rid of errors caused by name clashes, but it also provides some level of documentation. When we see code written in the unqualified style like this:

import Html exposing (Html, text)
import List exposing (isEmpty)
import MyList exposing (..)
import SomeOtherModule exposing (..)


main : Html msg
main =
    isEmpty list2
        |> toString
        |> text

It’s not obvious where isEmpty comes from and what it does. Does it come from List, MyList, or SomeOtherModule? But if we use the qualified style, we know exactly where isEmpty comes from. We might even be able to deduce what it does based on its origin.

import Html
import List
import MyList
import SomeOtherModule


main : Html.Html msg
main =
  List.isEmpty list2
    |> toString
    |> Html.text

Now we know that isEmpty definitely comes from the List module and it checks whether a list is empty or not. But there are scenarios where using the unqualified style could make our code more readable. In Building a Simple Web Page section, we wrote the following code.

module BeginningElm exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)


view : a -> Html msg
view model =
    div [ class "jumbotron" ]
        [ h1 [] [ text "Welcome to Dunder Mifflin!" ]
        , p []
            [ text "Dunder Mifflin Inc. (stock symbol "
            , strong [] [ text "DMI" ]
            , text ") is a micro-cap regional paper and office
                    supply distributor with an emphasis on
                    servicing small-business clients."
            ]
        ]


main : Html msg
main =
    view "dummy model"

As you can see, the Html and Html.Attributes modules are imported using the unqualified style. The reason behind that is, these two modules contain non-overlapping functions. Therefore, the chances of function names clashing over each other is minimal. Furthermore, the function names in these modules resemble HTML markup pretty closely which makes reading the code that represents HTML in Elm quite intuitive. But if we were to write the code above in qualified style, it would be hard to read.

module BeginningElm exposing (..)


import Html
import Html.Attributes


view : a -> Html msg
view model =
    Html.div [ Html.Attributes.class "jumbotron" ]
        [ Html.h1 [] [ Html.text "Welcome to Dunder Mifflin!" ]
        , Html.p []
            [ Html.text "Dunder Mifflin Inc. (stock symbol "
             , Html.strong [] [ Html.text "DMI" ]
             , Html.text ") is a micro-cap regional paper and office
                          supply distributor with an emphasis on
                          servicing small-business clients."
            ]
        ]


main : Html msg
main =
    view "dummy model"

If you do decide to use the unqualified style, I recommend limiting it to one exposing (..) per file. If there is a family of modules that often get used together such as Html and Html.Attributes where the chances of name clashes are minimal and the function names are self-documenting then it’s safe to use more than one exposing (..).

Module Names with Prefix
Notice how the Html.Attributes module has a prefix called Html in its name? That’s because it resides in a package called Html which contains multiple modules:
  • Html
  • Html.Attributes
  • Html.Events
  • Html.Keyed
  • Html.Lazy

When we import a module from a package that has multiple modules in it, we have to add the package name as a prefix except when the module name is the same as the package name, for example the Html module. In that case, the package name essentially becomes a part of the module name. One exception to this rule is the Core package. It contains multiple modules, but when we import those modules we don’t have to prefix them with Core, for example import Array.

As Syntax

If a module name is long, writing code in qualified style gets tedious and the final code looks verbose. For example, Elm provides a module called PageVisibility whose name is a bit longer than some of the modules we have seen thus far. Take a look at the following code that uses this module. Don’t worry about what the code actually does, just pay attention to all the places the name PageVisibility appears.

import PageVisibility


type Msg =
    VisibilityChanged PageVisibility.Visibility


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        VisibilityChanged PageVisibility.Visible ->
            ...

        VisibilityChanged PageVisibility.Hidden ->
            ...


subscriptions : Model -> Sub Msg
subscriptions model =
    PageVisibility.visibilityChanges VisibilityChanged

We can reduce the clutter by introducing a shorter alias for PageVisibility using the as syntax.

import PageVisibility as PV


type Msg =
    VisibilityChanged PV.Visibility


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        VisibilityChanged PV.Visible ->
            ...

        VisibilityChanged PV.Hidden ->
            ...


subscriptions : Model -> Sub Msg
subscriptions model =
    PV.visibilityChanges VisibilityChanged

Looks better, doesn’t it? It also strikes a good balance between conciseness and self-documentation. PV is much more concise compared to PageVisibility, but we also know where the visibilityChanges function comes from. It’s prefixed with PV which is an alias for PageVisibility, so it must be from that module.

We can combine the as syntax with unqualified imports like this:

import PageVisibility as PV exposing (visibilityChanges)

The alias must come before exposing, otherwise, Elm will throw an error.

-- This will throw an error

import PageVisibility exposing (visibilityChanges) as PV

Or we can put them on separate lines.

import PageVisibility as PV
import PageVisibility exposing (visibilityChanges)

Making Functions Private

Writing private functions in Elm is easy. Just don’t expose them. Let’s say the isEmpty function in the MyList module delegates the task of determining whether a list is empty or not to a different function, and we don’t want the world to know about this little secret. Add a new function definition below isEmpty in MyList.elm and delegate the responsibility of checking emptiness to it.

module MyList exposing (MyList(..), sum, isEmpty)
.
.

isEmpty : MyList a -> Bool
isEmpty xs =
    isItReallyEmpty xs


isItReallyEmpty : MyList a -> Bool
isItReallyEmpty xs =
    case xs of
        Empty ->
            True

        _ ->
            False

isItReallyEmpty is a private function because it’s not added to the list of values that are exposed. Let’s try to access it anyway from Playground.elm to see what happens. Change the content of the main function to this:

main : Html msg
main =
    MyList.isItReallyEmpty list2
        |> toString
        |> Html.text

If you refresh the page at http://localhost:8000/elm-examples/Playground.elm, Elm appropriately points out that it doesn’t recognize what isItReallyEmpty is.

If you didn’t get the error, make sure the MyList module doesn’t expose everything. Its definition should look like this:

module MyList exposing (MyList(..), sum, isEmpty)
.
.

To get rid of the error, replace isItReallyEmpty with isEmpty in the main function.

main : Html msg
main =
    MyList.isEmpty list2
        |> toString
        |> Html.text

It’s best practice to keep functions private unless you must expose them. Once they are exposed to the outside world, it can be very risky to change them. If we modify the input and/or output format of a function with no backward compatibility, the client code using that function could stop working. But if a function is private, we can refactor it without the fear of breaking some code out there.

If you’re worried about the list of values exposed in a module definition getting too long, don’t be. Most Elm modules expose a lengthy list of values. Here’s how the List module exposes its values:

module List exposing
    ( isEmpty, length, reverse, member
    , head, tail, filter, take, drop
    , singleton, repeat, range, (::), append, concat, intersperse
    , partition, unzip
    , map, map2, map3, map4, map5
    , filterMap, concatMap, indexedMap
    , foldr, foldl
    , sum, product, maximum, minimum, all, any, scanl
    , sort, sortBy, sortWith
    )

Unfortunately, as of this writing, elm-format pushes each exposed value into a new line when you save the file making it hard to read. Hopefully, that’ll change in the future.

module List
    exposing
        ( isEmpty
        , length
        , reverse
        , member
        , head
        , tail
        , filter
        .
        .
        )

To summarize, Elm makes it very easy to group functions, constants, type definitions, and other values together using modules. The syntax for creating and importing modules is straightforward. We can also share our modules with other programmers first by including them in a package and then publishing them through the online catalog. We’ll see some more examples of modules in the chapter that covers the Elm Architecture.

Back to top

New chapters are coming soon!

Sign up for the Elm Programming newsletter to get notified!

* indicates required
Close