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, 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
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
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
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
Add the following code to the
We created a module called
MyList that contains the definition for the
MyList type and two functions:
isEmpty. In the Recursive Types section, we had already defined
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
You already know how
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.
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
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
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
We’re now ready to use the
isEmpty function in the
Playground module. Add the following two constants right above
isEmpty function in
main to check if the list referenced by
list1 is empty or not.
If you refresh the page at
http://localhost:8000/elm-examples/Playground.elm, you should see “True.” Let’s apply
list2 to make sure that the false condition is working too.
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.
The reason why we need to prefix
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.
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
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.
Now we can get rid of the prefix in
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.
main? Can we get rid of the prefix there too? Sure. Just add
isEmpty to the list of values being exposed in
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
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.
(..) in various places throughout the book. Now you know what it means. This syntax also works on a file where the module is defined.
But, you don’t need to replace everything after
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:
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
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
main like this:
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
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
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.
- 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.
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:
It’s not obvious where
isEmpty comes from and what it does. Does it come from
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.
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.
As you can see, the
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.
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.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
- Module Names with Prefix
- Notice how the
Html.Attributesmodule has a prefix called
Htmlin its name? That’s because it resides in a package called Html which contains multiple modules:
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
Htmlmodule. In that case, the package name essentially becomes a part of the module name. One exception to this rule is the
Corepackage. It contains multiple modules, but when we import those modules we don’t have to prefix them with
Core, for example
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
We can reduce the clutter by introducing a shorter alias for
PageVisibility using the
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:
The alias must come before
exposing, otherwise, Elm will throw an error.
Or we can put them on separate lines.
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
MyList.elm and delegate the responsibility of checking emptiness to it.
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:
If you refresh the page at
http://localhost:8000/elm-examples/Playground.elm, Elm appropriately points out that it doesn’t recognize what
If you didn’t get the error, make sure the
MyList module doesn’t expose everything. Its definition should look like this:
To get rid of the error, replace
isEmpty in the
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:
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.
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.