In Elm all code resides in some module and all modules have their own files. A module can export one or more values, types etc. Understanding how this works will improve the readability and maintainability of our code bases.
The example we will be using throughout this article is a fictive project with this file structure:
$ tree . ├── elm.json └── src ├── Group │ ├── State.elm │ └── View.elm ├── Main.elm ├── Shared │ └── View.elm └── User ├── State.elm └── View.elm 3 directories, 6 files
First of all, a module's name must match its location.
This means that if you are importing a module from your code base that's called
Shared.View you know that this module is located in a file called
View.elm in a folder called
Because of this you never have to use relative paths to access modules.
Let's say we're in
User/View.elm and want to use some function from
import SharedView from '../Shared/View';
In my experience these paths are realistically much longer (for example
'../../../../../user/header') and this makes it hard to see at a glance where this module is actually located.
Is this the
UserHeader from our
Shared folder or our
Components folder or our
What's hiding behind all the dots?!
In Elm instead of giving some name to some module located at some path you just say which module you want:
import Shared.View -- Or our other example import Utils.User.Header
Choosing what to export
In Elm, like other languages, we can choose what we want to export from a module and what we want to keep internal.
The first line of a module must always start with
module, then the name of the module (which, as we saw, must match its location), then
exposing and a comma-separated list of what we want to make accessible to the outside world.
Any type or value not listed here will only be accessible inside the module.
module User.State exposing (Model, UserType(..), update)
This module exposing a type called
Model, a custom type called
UserType (and all its constructors) and something called
update (probably a function).
Choosing what to import
When you want to use something from another module there are two ways of doing it: qualified or unqualified.
Qualified means that you explicitly say which module the value is coming from, whereas unqualified means that you use the value "without a prefix".
Html.div  [ Html.text "Hello, world!" ]
div  [ text "Hello, world!" ]
For the qualified version to work you only need to put
near the top of your file.
If you want to use some value unqualified, there are two ways:
import Html exposing (div, text) -- only imports `div` and `text` -- or import Html exposing (..) -- imports everything from the Html module
Note that if you do
import Html exposing (div)
you can still access for example
text like so:
When to use what
The general rule-of-thumb is to always use qualified access.
When you read code someone else wrote (or maybe even your own from a couple of days ago), you'll have a hard time figuring out where the function
run is coming from.
If they (or you) instead wrote
Simulation.run you see at a glance which
run function is being used.
The one exception to this rule I use myself is when doing HTML stuff. I usually do something like
import Html exposing (div, p, text) import Html.Attributes exposing (class) import Html.Events exposing (onClick)
(..) for one or more of these.
This is because when reading view functions I find it easier to see what kind of HTML structure I'm trying to end up with.
Another cool trick is to give aliases to modules.
Let's go back to our original example with the
User folder containing
View.elm exposes some function
State.elm exposes some function
update, our initial attempt to use it might look like this:
import User.State import User.View -- ... update msg model = case msg of UserMsg userMsg -> let updatedUser = User.State.update userMsg model.user in -- ... -- ... view model = div  [ User.View.view model.user ]
If we instead imported the modules like this:
import User.State as User import User.View as User
we can do
User.update userMsg model.user -- and User.view model.user
Pretty neat, right? Bear in mind, though, that this will not work well if both of the modules export something that has the same name.
Aliasing external modules
Aliasing works just as well with external modules as with your own.
Another great use for it is when using for example
These modules (and the other
*-extra modules from elm-community) are made with aliasing in mind, so they have no exported members that collide with the built-in modules they are "augmenting".
import List.Extra as List import Random.Extra as Random
This allows you to use for example both
List.filter, which is from
List.find, which is from