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
