liveBook · Manning

Embedded domain-specific language

As explained in Functional Design and Architecture: Examples in Haskell

An embedded domain-specific language is a domain-focused language implemented within a host programming language and represented using the host's syntax or abstractions.

An embedded domain-specific language (eDSL) expresses a focused domain inside a host language rather than inventing a separate syntax. Its vocabulary names meaningful actions, data, or rules, letting code read as a model of the work instead of a tangle of low-level calls. The goal is a smooth translation of domain meaning into code while limiting accidental complexity. section 5.1.1 section 1.3

In functional declarative design, an eDSL commonly encodes actions as pure values, then hands those descriptions to an interpreter that performs impure work. This separates a script’s intent from device access, state handling, protocols, or other runtime details. The script stays readable and composable; the interpreter is the boundary where descriptions become effects. section 2.2.1 section 2.2.2

Smart-home action language

A smart-home script can list actions such as reading named thermometers and barometers, reporting temperature or pressure, and clearing collected data. The language has a small fixed set of constructors, while a script sequences them to describe a useful workflow. An interpreter traverses that sequence, calls sensor libraries, maintains measurements, and reports results. This exposes a key trade-off: the domain script is simple, but an interpreter must handle every action and its stateful effects. section 2.2.2

Listing 2.1 Domain-specific language for a smart house
type ThermometerName = String
type BarometerName = String
  
data Language                          #1
  = ReadThermometer ThermometerName
  | ReadBarometer BarometerName
  | ReportTemperature
  | ReportAtmospherePressure
  | ClearData
  
type Script = [Language]               #2
  
script :: Script
script =
  [ ReadThermometer "Garage"           #3
  , ReadThermometer "Near the road"
  , ReadThermometer "House"
  , ReadBarometer "Garage"
  , ReadBarometer "House"
  , ReportTemperature
  , ReportAtmospherePressure
  , ClearData
  ]

Inspect the action vocabulary and the script separately: the listing shows domain intent as data before any sensor or reporting implementation is chosen. section 2.2.2

An eDSL is useful only when its boundaries match the domain. Poorly designed languages can become restrictive or add complexity, whereas domain-centered languages make requirements, responsibilities, and permitted combinations more visible. Organize larger designs by meaning and scope, separate responsibilities, and use multiple related languages when one vocabulary would otherwise become muddled. section 3.1.2 section 5.4

Common roles for embedded DSLs include:

  • Modeling a business subsystem as a formal vocabulary that can also serve as domain documentation. section 3.1.2
  • Describing stateless hardware templates or blueprints that can be compiled into different runtime representations for testing or production. section 3.3.1
  • Declaring typed HTTP routes, request bodies, responses, and captures, then interpreting that schema into server routes, clients, or documentation. section 14.1.1 section 14.2.1

Algebraic data types are a practical foundation for functional eDSLs: constructors make domain alternatives explicit and can model both complex structures and business concepts. Type-level combinators offer another embedding style, as with an HTTP schema whose syntax exposes routes and payloads without requiring users to know its internal implementation. Functional languages make these approaches especially convenient, but embedded DSLs are not exclusive to them and can also be created in object-oriented languages. section 5.2 section 14.1.1 section 1.5.3

Treat an eDSL as a deliberate design tool, not a universal abstraction. It can decouple domain descriptions from implementations and reduce complexity, but it should preserve the real domain rather than obscure it behind generic machinery. When interpretable descriptions, clear responsibilities, and runtime boundaries fit the problem, the resulting design can be clearer, safer, and easier to evolve. section 2.3 section 5.4

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Explains ADT and type-level eDSL forms, interpreters, organization, and practical trade-offs without detailing a general implementation recipe.
Examples
Uses smart-home sensor scripts, spaceship hardware blueprints, and Servant HTTP API schemas with routes, bodies, and handlers.
What it assumes
Assumes familiarity with programming and introduces eDSLs through domain models, algebraic data types, and concrete scripts.
What you can do after
Evaluate/understand how interpretable domain descriptions decouple intent from runtime effects and reduce accidental complexity.
How it connects
Connects algebraic data types, interpreters, domain-driven design, accidental complexity, functional programming, and HTTP schemas.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.