Understanding how hx projects are organized.
Standard Layout
A typical hx project looks like:
my-project/
├── app/
│ └── Main.hs # Application entry point
├── src/
│ └── MyProject.hs # Library source
│ └── MyProject/
│ ├── Parser.hs
│ └── Types.hs
├── test/
│ └── Spec.hs # Test suite
├── bench/
│ └── Main.hs # Benchmarks (optional)
├── dist-newstyle/ # Build artifacts (gitignored)
├── .hx/ # hx metadata (gitignored)
├── my-project.cabal # Cabal package description
├── hx.toml # hx configuration
├── hx.lock # Dependency lockfile
├── CHANGELOG.md
└── README.mdKey Files
hx.toml
Project configuration for hx:
[project]
name = "my-project"
version = "0.1.0"
[toolchain]
ghc = "9.8.2"
[build]
ghc-options = ["-Wall"]my-project.cabal
Cabal package description (source of truth for:
- Package metadata
- Dependencies
- Component definitions
cabal-version: 3.0
name: my-project
version: 0.1.0
library
exposed-modules: MyProject
hs-source-dirs: src
build-depends: base ^>=4.18
executable my-project
main-is: Main.hs
hs-source-dirs: app
build-depends:
, base ^>=4.18
, my-projecthx.lock
Lockfile with pinned dependency versions:
version = 1
generated = "2024-01-15T10:30:00Z"
[[package]]
name = "text"
version = "2.0.2"
sha256 = "..."Source Directories
src/
Library source code:
src/
└── MyProject.hs # MyProject module
└── MyProject/
├── Parser.hs # MyProject.Parser
├── Types.hs # MyProject.Types
└── Internal.hs # MyProject.Internal (not exported)Module naming convention:
- Top-level:
src/Foo.hs→Foo - Nested:
src/Foo/Bar.hs→Foo.Bar
app/
Executable source code:
app/
└── Main.hs # main entry pointFor multiple executables:
app/
├── Main.hs # my-project executable
└── CLI.hs # my-project-cli executabletest/
Test code:
test/
├── Spec.hs # Test entry point
├── MyProject/
│ ├── ParserSpec.hs # Parser tests
│ └── TypesSpec.hs # Types tests
└── TestUtils.hs # Test utilitiesbench/
Benchmark code:
bench/
└── Main.hs # Benchmark entry pointBuild Artifacts
dist-newstyle/
Cabal build directory (gitignored):
dist-newstyle/
├── build/ # Compiled objects
│ └── x86_64-osx/
│ └── ghc-9.8.2/
│ └── my-project-0.1.0/
├── cache/ # Cabal cache
├── packagedb/ # Local package database
└── tmp/ # Temporary files.hx/
hx-specific metadata (gitignored):
.hx/
├── cache/ # hx cache
├── bhc.toml # Generated BHC manifest (if using BHC)
└── bhc-reports/ # BHC optimization reportsConfiguration Files
.gitignore
Standard gitignore:
# Build artifacts
dist-newstyle/
dist/
.cabal-sandbox/
cabal.sandbox.config
# hx
.hx/
# Editor
*.swp
*~
.vscode/
.idea/
# GHC
*.hi
*.o
*.dyn_hi
*.dyn_o.hlint.yaml
HLint configuration:
- ignore: {name: "Use camelCase"}
- modules:
- {name: Data.Map.Strict, as: Map}fourmolu.yaml
Fourmolu configuration (if used):
indentation: 2
function-arrows: leadingProject Variants
Library Only
my-lib/
├── src/
│ └── MyLib.hs
├── test/
│ └── Spec.hs
├── my-lib.cabal
└── hx.tomlExecutable Only
my-app/
├── app/
│ └── Main.hs
├── my-app.cabal
└── hx.tomlMulti-Package (Workspace)
my-workspace/
├── packages/
│ ├── core/
│ │ ├── src/
│ │ └── core.cabal
│ ├── cli/
│ │ ├── app/
│ │ └── cli.cabal
│ └── web/
│ ├── src/
│ └── web.cabal
├── cabal.project
├── hx.toml
└── hx.lockCabal Components
Library
library
exposed-modules:
MyProject
MyProject.Parser
other-modules:
MyProject.Internal
hs-source-dirs: src
build-depends:
, base ^>=4.18
, text ^>=2.0Executable
executable my-project
main-is: Main.hs
hs-source-dirs: app
build-depends:
, base ^>=4.18
, my-projectTest Suite
test-suite my-project-test
type: exitcode-stdio-1.0
main-is: Spec.hs
hs-source-dirs: test
build-depends:
, base ^>=4.18
, my-project
, hspec ^>=2.11Benchmark
benchmark my-project-bench
type: exitcode-stdio-1.0
main-is: Main.hs
hs-source-dirs: bench
build-depends:
, base ^>=4.18
, my-project
, criterion ^>=1.6Best Practices
1. Separate Library and Executable
Put reusable code in the library:
-- src/MyProject.hs
module MyProject (run) where
run :: IO ()
run = putStrLn "Hello"Keep executable thin:
-- app/Main.hs
module Main where
import MyProject (run)
main :: IO ()
main = run2. Use Internal Modules
Hide implementation details:
library
exposed-modules: MyProject
other-modules: MyProject.Internal3. Consistent Naming
- Module names match directory structure
- Test modules mirror source modules
- Descriptive executable names
4. Keep Root Clean
Minimize files in project root. Use subdirectories for organization.