liveBook · Manning

Device

As explained in Functional Design and Architecture: Examples in Haskell

A device is a runtime abstraction representing a physical hardware component and the operations for connecting to it, reading measurements, and evaluating commands.

A device is a runtime abstraction for a physical hardware component. It knows how to connect to hardware, read measurements, and evaluate commands, much like an operating-system file handle. An Hdl definition is translated into a Device value, but treating every Hdl script as one device creates a design flaw: declarative language definitions and runtime hardware instances are different concerns. section 3.3.3 section 5.3.1

A functional interface keeps the Device implementation hidden behind abstract types and operations. Construction recursively traverses component definitions, while functions such as addSensor, addController, getDevicePart, and updateDevicePart manipulate the opaque value. This boundary lets the interpreter depend on the interface rather than on storage details. section 3.3.3 section 4.1.3

A practical Device design separates several responsibilities:

  • Store parts addressed by component indexes, with sensor and controller implementations represented separately. section 3.3.3 section 4.1.3
  • Attach vendor-specific contexts when constructing the runtime value; these contexts may contain network connections, internal state, or actors. section 4.1.3
  • Expose component capabilities through APIs: sensors can reset, read measurements, and set callbacks; controllers can reboot, stop, and evaluate commands. section 3.3.3
  • Treat lifetime explicitly: a runtime instance may outlive a script, or be disposed when its scope ends; controller presence can determine device visibility. section 5.3.1

Building a device from a hardware definition

A boosters definition can list temperature sensors, pressure sensors, and a controller. makeDevice walks that list, validates each component passport against available vendor components, and inserts the resulting implementation under its component index. The resulting Device can then expose a component for measurement or command evaluation without revealing how vendor handlers are stored. section 4.1.3 section 3.3.3

Listing 3.12 Hardware runtime
module Andromeda.Hardware.Device ( 
    Device,                                      #1
    DevicePart,
    makeDevice,                                  #2
    blankDevice,
    addSensor,
    addController,
    getDevicePart,
    updateDevicePart
 ) where
  
import Andromeda.Hardware.Common
import Andromeda.Hardware.Hdl
  
data DevicePart                                  #3
  = SensorImpl     ComponentPassport
  | ControllerImpl ComponentPassport
  
type DeviceParts = Map ComponentIndex DevicePart #4
  
data Device = DeviceImpl DeviceParts             #5
  
makeDevice :: Hdl -> Device                      #6
blankDevice :: Device
addSensor :: ComponentIndex -> Parameter 
          -> ComponentPassport -> Device -> Device 
addController :: ComponentIndex -> ComponentPassport
              -> Device -> Device 
getDevicePart :: ComponentIndex -> Device
             -> Maybe DevicePart 
updateDevicePart :: ComponentIndex -> DevicePart
                 -> Device -> Maybe Device

Inspect the runtime module boundary, including the opaque Device type, DevicePart representation, construction functions, and component access operations. section 3.3.3

Listing 3.13 Connecting the real API
import Andromeda.Vendors.AAA                              #1
  ( t25SensorName, p02SensorName
  , t25Handler, p02Handler)                               #2
 
addSensor :: ComponentIndex -> Parameter                  #3
          -> ComponentPassport -> Device -> Device
addSensor
  idx _
  def@(ComponentPassport _ cName _ _)                     #4
  (DeviceImpl components) = let                           #5
 
      handler     = getHandler cName                      #6
      sensor      = SensorImpl def handler                #7
      components' = Map.insert idx sensor components      #8
 
      in DeviceImpl components'                           #9
 
getHandler :: ComponentName -> SensorAPI                  #10
getHandler cName | cName == t25SensorName = t25Handler
getHandler cName | cName == p02SensorName = p02Handler
getHandler _     | otherwise = error "unknown component"

Inspect how a sensor component is connected to a real vendor handler while the Device machinery remains separate from vendor-specific APIs. section 3.3.3

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Explains construction, hidden representations, vendor contexts, APIs, and lifetimes, while deferring mutable state organization.
Examples
Uses a boosters definition, indexed sensor and controller parts, vendor handlers, and script-scoped controller instances.
What it assumes
Assumes Hdl definitions, component passports, indexes, and functional programming concepts such as recursion and externally supplied state.
What you can do after
Build it from an Hdl definition, connect vendor APIs, retrieve components, read measurements, and evaluate controller commands.
How it connects
Connects Device with Hdl, functional interfaces, interpreters, APIs, sensors, controllers, vendor components, and runtime state.

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.