DeviceControl is an embedded language for hardware interaction: it describes requests to obtain a controller’s status or read a component’s sensor. Rather than performing device I/O directly, its operations are represented as values that a surrounding control language can run and interpret. This separation keeps device-facing commands focused on hardware concerns while allowing higher-level logic to decide what to do with results. section 5.2.3 section 6.4.2
The two core operations carry their expected result types. GetStatus returns either a HardwareFailure or a ControllerStatus; ReadSensor returns either a HardwareFailure or a Measurement. Encoding these result types in the constructors prevents an interpreter from returning an unrelated value and makes failure handling visible to the calling logic. section 6.4.2
Listing 6.13 DeviceControl type
{-# LANGUAGE GADTs #-}
data DeviceControlMethod a where #1
GetStatus
:: Controller
-> DeviceControlMethod (Either HardwareFailure ControllerStatus)
ReadSensor
:: Controller
-> ComponentIndex
-> DeviceControlMethod (Either HardwareFailure Measurement)
The operation definitions show the language boundary: each hardware request specifies both its inputs and the exact successful-or-failed result available to later logic. section 6.4.2
DeviceControl belongs below LogicControl in a hierarchy of eDSLs. LogicControl can evaluate a DeviceControl operation, then continue with its typed result; DeviceControl itself need not know the outer language. This direction avoids the earlier arrangement in which device definition, device interaction, and reporting languages recursively embedded one another and formed cyclic dependencies. section 5.2.3 section 6.4.2
A practical design progression is:
- Start by separating device definition, device interaction, and reporting into distinct language responsibilities. section 5.2.3
- Avoid mutually nesting scripts across those languages, because the resulting cycle tightly couples modules and obscures the control flow. section 5.2.3
- Parameterize inner ADTs by their continuation when a subordinate language must resume an outer script without naming it directly. section 5.2.4
- Use free-monad operations and typed constructors so LogicControl can lift a device request and receive its particular result safely. section 6.4.2
Reading two booster statuses
A control script can first create two controllers through Hdl, then call getStatus for each controller. It combines two successful ControllerStatus values into one result; if either device request returns HardwareFailure, it converts that failure into LogicFailure. DeviceControl supplies the hardware results, while LogicControl owns the workflow and error policy. section 6.4.2
Smart constructors such as getStatus and readSensor provide the higher-level entry points. They lift DeviceControlMethod requests into LogicControl, preserving the operation’s result type without exposing callers to the lower-level constructor directly. An interpreter then maps each request to IO and must produce the result required by that request’s type. section 6.4.2
This structure is useful when device interaction must remain independently understandable and testable while participating in larger automation. It does not attempt to model calculations, physics, or all automation behavior; it establishes a small typed boundary for status and sensor access that broader control logic can compose. section 6.4.2
