jvoegele

jvoegele

Bond 1.7.0 is out. :tada: It’s been four minor releases since I last posted here (1.3.0), and the theme of that run is reuse and testing: contracts you can name and share, contracts that quantify over collections, and — new in 1.7.0 — contracts that drive your property tests. Here’s the whole story since 1.3.0.

def deps do
  [{:bond, "~> 1.7"}]
end

New to Bond? It brings Design by Contract to Elixir — @pre/@post/@invariant checked at runtime, with failure messages that tell you exactly what was violated and why. The original announcement and the getting-started guide are the best on-ramps.

Quantified assertions (1.4.0)

forall / exists let an assertion quantify over a collection, with comprehension-style generator syntax:

@pre all_positive: forall(x <- items, x > 0)
@post sorted: forall(i <- 0..(length(result) - 2)//1,
                     Enum.at(result, i) <= Enum.at(result, i + 1))

Unlike Enum.all?/2 / Enum.any?/2, a failure reports which element broke the predicate and its index — not just that the whole expression was false:

|   assertion: forall(x <- items, x > 0)
|   counterexample: element at index 3 (-2) does not satisfy `x > 0`

Both short-circuit, return ordinary booleans (so they compose with and/or/not/~>), and work in @pre, @post (including over result), @invariant, and Bond.check/1.

Reusable named contracts (1.5.0 + 1.6.0)

Inspired by discussion with @petr: declare a bundle of @pre/@post once, under a name, and apply it to many functions — in the same module or across modules.

defmodule Money do
  use Bond

  defcontract withdrawal(account, amount) do
    @pre positive: amount > 0
    @pre sufficient: amount <= account.balance
    @post non_negative: result.balance >= 0
  end
end

defmodule Account do
  use Bond

  @apply_contract {Money, :withdrawal}
  def withdraw(acct, amt), do: %{acct | balance: acct.balance - amt}
end

The contract’s head supplies canonical argument names; an applying function’s parameters rebind to them positionally (so it can name them whatever it likes), exactly as an implementation inherits a behaviour callback’s contract. Violations are attributed to their source — precondition (from contract Money.withdrawal) failed … — with the originating {module, name} exposed on the error struct and in the [:bond, :assertion, :failure] telemetry.

1.6.0 then made named contracts composable. A function may add its own @pre/@post alongside @apply_contract, and a defcontract can include other contracts so small focused agreements build into larger ones:

defcontract positive(x),         do: (@pre x > 0)
defcontract in_range(v, lo, hi), do: (@pre lo <= v and v <= hi)

defcontract order(item) do
  include positive(item.quantity)
  include in_range(item.discount, 0, 100)
  @post priced: result.total >= 0
end

Each include argument is an expression over the host’s parameters, substituted into the included clauses (so error messages show the substituted form, e.g. item.quantity > 0). Includes nest transitively; a cycle is a compile error. Full rules and examples: the Reusable Contracts guide.

New in 1.7.0: contract-driven testing

A contract is a runtime predicate that fires at every call site — which makes it a ready-made test oracle. Bond has paired with StreamData since 1.0 (contract_holds/2, invariants_hold/2), and 1.7.0 adds probe_contract/2, which turns a function’s own @pre into the driver:

use Bond.PropertyTest

probe_contract &Account.withdraw/2, args: [account_gen(), StreamData.integer(0..100)]

Two things make it different from contract_holds/2:

  • Boundary probing. Bond reads the literal comparisons in the function’s @pre (e.g. amount >= 0, amount <= 100) at compile time and mixes the implied boundary values into your generators — so the property hits the edges, where off-by-one postcondition bugs live, deliberately rather than by chance.
  • @pre as a filter. An input that violates the precondition is discarded (a generation miss, not a failure) instead of failing the property — so you can generate broadly and still exercise only valid inputs, with the @post as the free oracle.

It’s inspired by Eiffel’s AutoTest, which likewise derives test cases from a routine’s contract and uses the contract itself as the pass/fail oracle. There’s a new Testing Contracts guide covering the whole testing surface — the example-based Bond.Test assertions and all three Bond.PropertyTest macros — and when to reach for each.

Stability

Everything since 1.3.0 is additive — no breaking changes — so each of these was a normal minor release and the new surface is covered by Bond’s stability guarantees. Compatibility is verified across Elixir 1.16–1.20 in CI.

Thanks & feedback

Named contracts came directly out of forum feedback, and the whole run was shaped by dogfooding these features in a real application — thank you. Questions, bug reports, and ideas are very welcome here or on the issue tracker.

Links

Showing Posts 1 to 1

jvoegele

jvoegele OP

Bond 1.8.0 is out, adding Bond.Server — Design by Contract for GenServer process state.

Bond’s struct @invariant constrains every value of a type. Bond.Server constrains the state of a running process: use GenServer + use Bond.Server, and declare module-wide invariants that Bond checks around your callbacks.

defmodule Counter do
  use GenServer
  use Bond.Server

  @state_invariant      non_negative: state.count >= 0
  @transition_invariant monotonic:    new_state.count >= old_state.count

  @impl true
  def init(n), do: {:ok, %{count: n}}

  @impl true
  def handle_call(:inc, _from, state), do: {:reply, :ok, %{state | count: state.count + 1}}
end
  • @state_invariant — a property of the server’s state, checked after every state-transition callback returns. Because it wraps the callbacks themselves, it catches the common case of a callback mutating state inline ({:noreply, %{state | ...}}) — something a struct @invariant never sees.
  • @transition_invariant — relates the prior state (old_state) to the next (new_state) across every transition. “The counter never decreases” is exactly the kind of temporal property you can’t soundly assert over shared Agent state, but which is race-free inside a serialized GenServer.

Both reuse the existing :invariants configuration — the precondition ≤ postcondition ≤ invariant chain, Bond.Config runtime toggling, and :purge to compile them out — and a violation raises Bond.InvariantError, tagged by :kind.

{:bond, "~> 1.8"}

Docs

— All posts loaded —

Where Next? Top

Trending in News & Updates Top

sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
bartblast
I’ll be using this thread to share Hologram patch release announcements. Minor releases will continue to get dedicated threads with blog ...
New
sorenone
This release unifies configuration for queues, repos, and services, swaps opaque timing integers for readable durations, and backports pe...
New
pcharbon
:heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::heart::hea...
New
webofbits
Aludel 0.7.0 is released :tada: Since 0.5.0, Aludel has grown into a much more complete LLM evaluation toolkit for Elixir and Phoenix app...
New
nature
NatureWhistle v0.4.1 is out! :tada: This release is a pretty significant step forward for the project. When I first built NatureWhistle,...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews