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 for solving 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 in this section.

A value is the most basic concept in Elm. 1, a, "Hannibal", and [ 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

Note: You can find the source code for all modules included in the elm/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/src directory and add the code below to it.

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


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 again. 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 [].

Note: 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, isEmpty, and sum 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.

It’s important to note that Elm doesn’t allow us to export only a subset of data constructors from a type. Therefore, the following code is invalid.

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

We tried to expose only Empty from the MyList type. Elm doesn’t allow that because data constructors are primarily used for pattern matching and pattern matches need to be exhaustive. Take the sum function from the Recursive Types section for example.

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

        Node intValue remainingNodes ->
            intValue + sum remainingNodes

If Elm had allowed us to expose only Empty, we would have had to use the catch-all-pattern to account for the Node constructor. That would have prevented us from correctly implementing the sum function.

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

        _ ->
            ...

Isn’t it great that the designers of Elm have put so much thought into how to guide us toward good code? You’ll encounter many more smart decisions like this as you learn more about Elm.

Importing a Module

To be able to use the new isEmpty function outside of MyList module, we need to import MyList. Let’s do that in Playground.elm.

module Playground exposing
.
.
import MyList

We can verify that MyList was imported successfully by loading the Playground module up in a browser. Run elm reactor from the beginning-elm directory in terminal if it isn’t running already, and go to http://localhost:8000/src/Playground.elm. If you don’t see any errors, that means the module was imported successfully. We’re now ready to use the isEmpty function in the Playground module. Add the following code right above main in Playground.elm.

list1 : MyList.MyList a
list1 =
    MyList.Empty


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

Now, call the isEmpty function from main to check if the list referenced by list1 is empty or not.

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

Refresh the page at http://localhost:8000/src/Playground.elm and you should see True. Let’s apply isEmpty to list2 to make sure that the false condition is also working.

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

Refresh the page at http://localhost:8000/src/Playground.elm and you should see False. The way we are creating lists now is slightly 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 the MyList type is now being used inside a code file instead of the repl. Before, when we imported the Playground module into repl, we used (..) after the exposing keyword so that everything inside that module is exposed.

> import Playground exposing (..)

This allowed us to drop MyList 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(..)) to the end of the line that imports MyList in Playground.elm.

module Playground exposing
.
.
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(..), isEmpty, sum)


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

Now we can get rid of the prefix from list1 and list2 in Playground.elm.

list1 : MyList a
list1 =
    Empty


list2 : MyList number
list2 =
    Node 9 Empty

Refresh the page at http://localhost:8000/src/Playground.elm and you should still see False. We were also able to remove the MyList 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?

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

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)
.
.

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

The figure below explains how the module import syntax works in detail.

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(..), isEmpty, sum)

At this point, we have exposed all values from the MyList module. There is actually a shorthand (..) for exposing everything in a module. Let’s use that instead of exposing each value individually.

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

You’ve seen (..) in various places throughout the book. Now you know what it means. We cannot, however, use the (..) shorthand to expose everything in a file where the module is defined. Syntactically speaking, the following code is invalid.

module MyList exposing (..)

But if you do type that code and save the file, elm-format will automatically replace .. with the names of all values in MyList.elm. By not allowing us to expose everything through the use of (..), Elm is encouraging us to carefully consider which values to expose from a module. We should expose as little as possible so that we don’t create unnecessary dependencies between modules. A code base with fewer dependencies is easier to maintain.

While we are at it, let’s get rid of the Html prefix too by exposing the text function and Html type in Playground.elm.

module Playground exposing
.
.
import Html exposing (Html, text)
.
.

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

It’s tempting to remove the Debug prefix too like this:

module Playground exposing
.
.
import Debug exposing (toString)
.
.
main : Html msg
main =
    isEmpty list2
        |> toString
        |> text

But we shouldn’t do that. The Debug module isn’t meant to be used in production. By not removing Debug as a prefix, we’re making it clear that this code needs to be removed or modified before it gets deployed to production.

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 List exposing (isEmpty)
import MyList exposing (..)
.
.

list3 : List a
list3 =
    []


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

Refresh the page at http://localhost:8000/src/Playground.elm and you’ll see the following error.

Elm is confused. It doesn’t know which isEmpty to use because we exposed it from both List and MyList. 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
        |> Debug.toString
        |> text

Refresh the page at http://localhost:8000/src/Playground.elm one more time and the error should go away.

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
        |> Debug.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
        |> Debug.toString
        |> text

Writing code in unqualified style is concise, but it’s also dangerous as we saw above with the name clash. The question is, when should we prefer qualified to unqualified? It’s best practice to write code in qualified style by default. Not only it gets rid of errors caused by name clashes, but also provides some level of documentation. When we see code written in 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
        |> Debug.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
    |> Debug.toString
    |> Html.text

Now we know that isEmpty definitely comes from List 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 HomePage exposing (main)

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 (main)

import Html
import Html.Attributes


view : a -> Html.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.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, to import the Array module all we need to type is import Array and not import Core.Array.

As Syntax

If a module name is long, writing code in qualified style gets tedious and the final code looks verbose. Let’s say we have 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

Making Functions Private

Making functions private is quite easy in Elm. All we have to do is not 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(..), isEmpty, sum)
.
.

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
        |> Debug.toString
        |> Html.text

If you refresh the page at http://localhost:8000/src/Playground.elm, Elm will point out that it doesn’t recognize what isItReallyEmpty is.

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

main : Html msg
main =
    MyList.isEmpty list2
        |> Debug.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 parameters or the return value 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 any 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
    ( singleton, repeat, range, (::)
    , map, indexedMap, foldl, foldr, filter, filterMap
    , length, reverse, member, all, any, maximum, minimum, sum, product
    , append, concat, concatMap, intersperse, map2, map3, map4, map5
    , sort, sortBy, sortWith
    , isEmpty, head, tail, take, drop, partition, unzip
    )

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

module List exposing
    ( (::)
    , all
    , any
    , append
    , concat
    .
    .
    )

Summary

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 more examples of custom 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