How do you organize large Elm files?

I tend to organize my files in 3 ways:

  1. Level of abstraction
  2. Type
  3. Functionality

These aren’t mutually exclusive and can work in harmony together. Below are some examples from a recent elm gamejam project showcasing some of these approaches in practice. I’ve written a post here about lessons learned that may also be interesting.

Level of abstraction

I tend to organize my files from most abstract to least abstract. If I call out to another function then it will be defined lower down. That way I can scan things by looking at the top of the file and only need to go down if I care about implementation details.

This works really well with my approach to writing view code where I try to define a bunch of domain-specific helpers. For example the following code:

view : Model -> Html Msg
view model =
    case model of
        Loading ->
            loadingView

        Playing state ->
            playingView state

        Lost state ->
            lostView state


loadingView : Html Msg
loadingView =
  -- ...


playingView : GameState -> Html Msg
playingView state =
    Html.main_ []
        [ header state.energyHistory
        , gameBoard state.grid
        , controls state
        ]


lostView : GameState -> Html Msg
lostView state =
  -- ...


-- definitions for header, gameBoard, and controls go down below

By type

If a file contains multiple type definitions, I tend to group all the functions that act mainly on that type in one place (sorted by level of abstraction :wink: ). This makes it easy to browse and search. It also is easier if I ever want to pull that type into its own separate file eventually.

Notice how a have a separate section for fox types and operations in the game jam project mentioned earlier:

-- in Main.elm

-- FOX


type alias FoxConfig =
    { costOfLiving : Energy
    , birthCost : Energy
    , rabbitNutrition : Energy
    }


initialFoxConfig : FoxConfig
initialFoxConfig =
  -- ...


stepFox : FoxConfig -> Position -> Fox -> Grid -> Generator Grid
stepFox ({ costOfLiving } as foxConfig) position fox grid =
  -- ...

Functionality

Even when working with a single type, I might organize code around functionality, typically broken up by comments. So I might have a section for custom constructors, JSON decoders, random generators, etc. Think of how the docs in the core library break up a module by grouping similar functions under headers.

For example Grid.elm in my previously mentioned gamejam project has sections for constructors, random, operations, queries, and stats:

-- CONSTRUCTORS


fromList : List Cell -> Grid
fromList =
  -- ...

-- [more functions...]


-- RANDOM


generator : Generator Grid
generator =
  -- ...


-- OPERATIONS


move : { from : Position, to : Position } -> Cell -> Grid -> Grid
move { from, to } cell grid =
   -- ...

-- [more functions...]


-- QUERIES


nearbyRabbits : Position -> Grid -> List ( Position, Cell )
nearbyRabbits =
  -- ...

-- [more functions...]


-- STATS

type alias EnergyStat =
    { rabbits : Energy, foxes : Energy }


energyStats : Grid -> EnergyStat
energyStats grid =
  -- ...
5 Likes