Dhall is a programmable configuration language with a type system. One of its core features is the merge function, which provides exhaustive pattern matching on union types (sum types). If you forget a case, Dhall rejects the configuration at evaluation time.
Union Types: A Quick Review
A union type represents a value that can be one of several variants. In Haskell:
data Shell = Bash | Zsh | FishA value of type Shell is Bash, Zsh, or Fish. Pattern matching lets you handle each case:
shebang :: Shell -> String
shebang Bash = "#!/bin/bash"
shebang Zsh = "#!/bin/zsh"
shebang Fish = "#!/bin/fish"The compiler verifies you handled all cases. Miss one, and you get a warning (or error with -Werror).
Dhall's merge Function
Dhall has no pattern matching syntax. Instead, it provides merge, which takes a record of handlers (one per variant) and a union value:
let Shell : Type = < Bash | Zsh | Fish >
let shebang : Shell -> Text =
\(shell : Shell) ->
merge
{ Bash = "#!/bin/bash"
, Zsh = "#!/bin/zsh"
, Fish = "#!/bin/fish"
}
shell
in shebang Shell.Zsh
-- Evaluates to: "#!/bin/zsh"The record keys must match the union's variants. Dhall checks this at evaluation time. If you add a variant to Shell but forget to update the merge record, Dhall rejects the expression.
Union Types with Payloads
Union variants can carry data. In Haskell:
data Environment
= Prod
| QA Text -- story ticket ID
| Dev Text -- developer identifierPattern matching extracts the payload:
logServer :: Environment -> Text
logServer Prod = "logs.prod.example.com"
logServer (QA _) = "logs.qa.example.com"
logServer (Dev tag) = "logs." <> tag <> ".local"Merge with Payloads in Dhall
In Dhall, variants with payloads are declared with a type annotation. The handler becomes a function:
let Environment : Type = < Prod | QA : Text | Dev : Text >
let logServer : Environment -> Text =
\(env : Environment) ->
merge
{ Prod = "logs.prod.example.com"
, QA = \(ticket : Text) -> "logs.qa.example.com"
, Dev = \(tag : Text) -> "logs." ++ tag ++ ".local"
}
env
in logServer (Environment.Dev "mbbx6spp")
-- Evaluates to: "logs.mbbx6spp.local"Note the difference:
Prodhas no payload, so its handler is a plain valueQAandDevhaveTextpayloads, so their handlers are functions
Type Safety in Practice
Dhall's merge provides the same guarantees as Haskell's pattern matching:
- Exhaustiveness: Every variant must have a handler
- Type correctness: Each handler must return the same type
- Payload types: Handler functions must accept the declared payload type
This makes refactoring safe. Add a new variant to your union type, and Dhall will reject every merge that does not handle it.
When to Use Union Types
Union types model mutually exclusive options:
- Environment tiers (dev, staging, prod)
- Feature flags with configuration
- Protocol message types
- Error categories
The merge function turns these into computed values: hostnames, feature settings, timeout durations, or any other derived configuration.
Full Example: Feature Flags
let Feature : Type =
< Enabled
| Disabled
| RolloutPercent : Natural
>
let isEnabled : Feature -> Bool =
\(f : Feature) ->
merge
{ Enabled = True
, Disabled = False
, RolloutPercent = \(pct : Natural) -> Natural/isZero pct == False
}
f
let timeout : Feature -> Natural =
\(f : Feature) ->
merge
{ Enabled = 30
, Disabled = 0
, RolloutPercent = \(_ : Natural) -> 30
}
f
in { cacheEnabled = isEnabled Feature.Enabled
, cacheTimeout = timeout Feature.Enabled
, betaEnabled = isEnabled (Feature.RolloutPercent 25)
}
-- Evaluates to: { cacheEnabled = True, cacheTimeout = 30, betaEnabled = True }The merge function is Dhall's answer to pattern matching. It provides exhaustiveness checking without special syntax, using records and functions you already understand.
Susan Potter
Quant
Work with me
I spent the first half of my career building risk models and market data infrastructure at BNP Paribas, Bank of America, and Citadel, then fourteen years shipping production systems at scale. Now I bring both sides to quantitative trading. If you're a trading firm, family office, or fund looking to tighten the connection between your research ideas and your production trading systems, whether that's building validation pipelines, formalizing signal logic, or getting microstructure analytics into a deployable state, I'd like to hear what you're working on. Reach me at me@susanpotter.net.