I tend to organize my files in 3 ways:
- Level of abstraction
- Type
- 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 ). 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 =
-- ...