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

First Post! Switch mode

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

kip
I’m a bit excited to announce that Localize and friends are now at release 1.0. Even though it’s a 1.0 release, it stands on 8 years of w...
New
bartblast
After building Hologram and sharing updates across various places, I’ve realized there’s a lot happening that doesn’t always make it to t...
New
bartblast
Hologram v0.11 is out! Two headline features this release. First, Elixir regexes now run in the browser. They were server-only until now,...
New
nseaSeb
Just published search_ash 0.5.0 (with search_core 0.4.0) on Hex. What’s new: synonyms You can now declare a synonym dictionary per lang...
New
mudasobwa
After years of struggling I made StreamData dependency optional. Finitomata.ExUnit got testing primitives for Persistence. Full back co...
New
mudasobwa
MdexMultilineCells is an MDEx plugin enabling multi-line cells in Markdown tables with full inline/block Markdown rendering and automati...
New
sullyMusty
ActiveMemory 0.8.0 — in-memory tables that speak Ecto ActiveMemory 0.8.0 is now on Hex. ActiveMemory is an in-memory store built on ETS ...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
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
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement