View Source Bond.Protocol (Bond v1.2.1)
Declare @pre/@post contracts on a defprotocol's functions and have them enforced across
every implementation — present or future — at the dispatch boundary.
Like Bond.Behaviour, this is Design by Contract meeting the Liskov Substitution Principle: a
protocol is a promise about a family of implementations, and a contract is the formal content
of that promise. Unlike behaviour inheritance, nothing is required of the implementations — a
defimpl stays completely ordinary and needs no Bond awareness.
defprotocol Sized do
use Bond.Protocol
@post non_negative: result >= 0
@spec size(t) :: non_neg_integer()
def size(data)
end
defimpl Sized, for: List do
def size(list), do: length(list)
endA @pre/@post precedes the def it attaches to, exactly as a contract precedes the def
it attaches to in use Bond. The contract expressions reference the protocol function's
declared argument names (data above) and, in a @post, result (the return value).
How it works (Option B — dispatch-layer wrapping)
defprotocol generates a dispatch function — Sized.size(data) calls
Sized.impl_for!(data).size(data). Bond.Protocol wraps that one dispatch function, once, in
the protocol module: at @before_compile it marks the function defoverridable and redefines
it to evaluate the precondition, call super/… (the original dispatch), then evaluate the
postcondition. Because the wrap is on dispatch, it applies uniformly to every implementation,
and it survives protocol consolidation.
Diagnostics
A violation is attributed to the protocol and names the implementation the call resolved to:
the message reads postcondition (from protocol Sized, impl Sized.List) failed in Sized.size/1, with :source_protocol and :impl on the error struct and the
[:bond, :assertion, :failure] telemetry metadata.
Scope (v1)
Inherited contracts are immutable: implementations enforce the protocol's contracts verbatim
and cannot refine them (that is the refinement feature's job). Direct calls to a concrete
implementation module (Sized.List.size/1) bypass dispatch and are therefore not checked —
only calls through the protocol (Sized.size/1) are. old/1 in a protocol @post and
compile-time :purge of protocol contracts are not supported in v1; runtime configuration
(config :bond, … and Bond.Config) applies as usual.