Skip to content

Protocols for Builders

A builder needs more than APIs. A builder needs stable contracts.

A protocol names what a part can do without deciding what that part must be. A vector can be countable. An atom can be dereferenceable. A database can be queryable. A test session can be runnable and closeable. Code can depend on the ability rather than the provider.

The examples share one named lesson session on the main Hara site. Definitions from an earlier step therefore remain available for later experiments, while each displayed form remains complete enough to rerun.

Functions make things happen. Protocols make systems fit together.

Collection operations in Hara are protocol-backed. The same count call works across values that provide ICount.

{:vector (count [:a :b :c])
 :map (count {:a 1 :b 2})
 :list (count (conj (conj nil :first) :second))}

The caller asks one question: how many? It does not inspect the concrete implementation first.

A custom value can join the same vocabulary.

(do
  (defstruct Inventory [items label])

  (extend-type Inventory ICount
    (count [inventory]
      (count (field inventory :items))))

  {:label (field (Inventory [1 2 3 4] "Workshop") :label)
   :count (count (Inventory [1 2 3 4] "Workshop"))})

count did not gain an Inventory branch. Inventory provided the existing contract.

Lookup and membership are different questions. IFind can distinguish a missing key from a key whose value is nil.

(let [record {:present nil}]
  {:present (IFind/has? record :present)
   :missing (IFind/has? record :missing)
   :value (get record :present)})

A builder should choose the contract that preserves the distinction the system actually needs.

An atom participates in several contracts. Reading, replacing, atomically comparing, and watching state are separate abilities rather than one opaque object lifecycle.

(let [state (atom 41)]
  (IReset/reset state 42)
  {:ordinary (deref state)
   :protocol (IDeref/deref state)})

The ordinary functions are convenient language entry points. The protocols are the contracts that allow other values to participate.

Protocols are not limited to Hara’s foundation. A domain can name its own stable ability and support more than one implementation.

(do
  (defprotocol IDescribe
    (describe [value]))

  (defstruct Tool [name])
  (defstruct Material [name quantity])

  (extend-type Tool IDescribe
    (describe [tool]
      {:kind :tool
       :name (field tool :name)}))

  (extend-type Material IDescribe
    (describe [material]
      {:kind :material
       :name (field material :name)
       :quantity (field material :quantity)}))

  [(describe (Tool "saw"))
   (describe (Material "timber" 12))])

The calling code uses describe. New implementations can arrive later without changing that call.

The foundation course establishes the model. The family labs now apply it to larger parts of a real system.

Continue: Collection Protocols →{ .md-button .md-button—primary } Continue: State and Lifecycle →{ .md-button } Browse the Protocol Atlas →{ .md-button } Return to the learning paths →{ .md-button }