jvoegele

jvoegele

Bond - Design by Contract for Elixir

Announcing Bond, Design by Contract for Elixir

Bond provides support for contract programming (also known as “Design by Contract”) for Elixir.

The primary goal for Bond is to provide the most feature-complete and thoroughly documented Design by Contract library for Elixir, with a concise and flexible syntax for specifying contracts.

Current and planned features include:

  • Function preconditions with @pre
  • Function postconditions with @post
  • “old” expressions in postconditions
  • “check” expressions for arbitrary assertions within a function body
  • Predicates (such as implies? and xor) for use in assertions
  • Detailed assertion failure reporting
  • Incorporation of preconditions and postconditions into @doc for function
  • Conditional compilation of contracts per environment
  • More detailed assertion failure reporting, including color coding à la ExUnit
  • Invariants for structs and/or stateful processes (if possible)

For comparison to other Design by Contract libraries for Elixir, please see the history section of the Bond documentation.

Most Liked

sbuttgereit

sbuttgereit

Hi–

Interesting project. I saw this in the docs:

Contracts in the form of preconditions and postconditions are part of the public interface for a module in the same way that function signatures and typespecs are.

Is the intention that pre/post conditions replace traditional typespecs when used or should they be considered supplemental? If they are a replacement, do they interact with Dialyzer at all? Also, the set-theoretic types, assuming they land, also offer a manner of contract with functionality ramifications, are there thoughts about how this library would relate to that future?

I’ve not looked much beyond a quick search through the docs. But these are the questions I’d need to understand for adoption.

jvoegele

jvoegele

Hi @sbuttgereit,

Contracts are not intended to replace typespecs or set-theoretic types; rather, they are complementary mechanisms.

Typespecs are evaluated at compile-time or with a static analysis tool such as Dialyzer, whereas contracts as implemented by Bond are evaluated at run-time (although they are attached to functions at compile-time so must be valid Elixir code).

The closest analogue to contracts in native Elixir is guard expressions for functions, which allow for making rudimentary “assertions” about function arguments. Assertions in Bond are much more powerful and are not limited to the pre-defined set of functions that are allowed in guard expressions.

jarlah

jarlah

fun :slight_smile: <3

  @post is_hallo: result == :hallo
  def hello do
    :world
  end

in this case dialyzer is screaming at me already .. but its MUCH easier to actually get the error in your face .. than dialyzer warning about something that SHOULD fail .. but never does. ref this thread where dialyzer warns about something that never fails Dialyzer warning from hell: no_return when calling erl_tar.create - #2 by jarlah

iex(1)> TestBond.hello()
** (Bond.PostconditionError) postcondition failed in TestBond.hello/0
|   label: :is_hallo
|   assertion: result == :hallo
|   binding: [result: :world]

    (test_bond 0.1.0) lib/test_bond.ex:6: TestBond.hello/0
    iex:1: (file)
iex(1)> 

its probably more valuable in the ecto example i described above

Last Post!

jvoegele

jvoegele

Thank you for the feedback, @Asd. I’ve attempted to answer all of your questions below.

Why do you override the def, defp and @ functions? It makes this library inapplicable with other libraries (like decorators, for example). More traditional approach is to use @on_definition callback to collect data for each function and then @before_compile to replace implementations of the functions. This way it is compatible with other solutions.

As mentioned in the history section of the Bond docs the implementation of Bond is based on an older contracts library, ex_contract, from Dariusz Gawdzik. I carried over some of the code from ex_contract more or less directly.

However, you make a good point about compatibility with other libraries, so I will revisit the implementation and try to use @on_definition and @before_compile instead of overriding various Kernel macros.

Why do you use gen_statem? Module is always compiled in a single process.

I chose to use a finite state machine for keeping track of compile state. I chose gen_statem simply because it is already available with Erlang and I didn’t have to pull in an extra dependency for an FSM.

Bond.FunctionWithContract.function_id will fail when context atom in variable is not nil (for example code was generated by macro)

I will look into this and commit a fix.

It tries to insert assertions to every clause, which results in code failing to compile with cryptic error when assertion references a variable which is present only in one clause. I think that this is a problem with the design of the solution, not with the implementation. Is it expected?

This is an intentional design decision: to have preconditions and postconditions defined before the first clause of a function, and apply those preconditions and postconditions to every clause of that function.

Not everyone agrees with that design decision, though, as can be witnessed in the discussion on my unmerged pull request for ex_contract.

My opinion, informed by Bertrand Meyer’s writing on Design by Contract, and especially Object-Oriented Software Construction, is that contracts are part of the public interface for a module and its functions, and not simply an implementation aid to the developer of a module. To me this means that contracts should be declarative and specify the observable aspects of a function.

In the case of multi-clause functions, it is an implementation detail to use different names for parameters in the different clauses of the function, and that implementation detail is not even visible to callers of the function unless they look directly at the source code. I.e., it’s not in the docs and not part of the public interface for the function.

That said, I do think I could make improvements to Bond to make it easier to define contracts for multi-clause functions. I’m thinking of adding some sort of bind clause to assertions in Bond, which would allow for pattern matching on arguments and binding arguments to arbitrary names. (Incidentally, this might also help with defining invariants at the module level by providing a mechanism for invariants to reference function parameters and/or pattern match on function results, as discussed with @zachallaun above.)

In the meantime, the best approach is just to use consistent names for parameters in all clauses of a multi-clause function. If the generic name for a parameter is not sufficient in the context of the individual function clause, then an alias could be used. Something like the following, perhaps (untested):

@spec get(URI.t() | String.t()) :: term()
@pre uri_string_or_struct: is_binary(uri) or is_struct(uri, URI),
     uri_string_is_parseable: is_binary(uri) ~> URI.parse(uri)
def get(%URI{} = uri) do
  http_request(uri)
end

def get(uri = url_string) when is_binary(url_string) do
  get(URI.parse(url_string)
end

Note the alias of the uri parameter as url_string in the second clause of the get/1 function, which allows the generic name uri to be used in the contract but still allows for a more meaningful name for that specific function clause.

Predicates work not only with booleans, but their spec states otherwise. Why?

This was simply an oversight on my part. I’ve updated the specs to look like this instead:

@spec xor(as_boolean(term()), as_boolean(term())) :: boolean()
@spec implies?(as_boolean(term()), as_boolean(term())) :: boolean()

This will be in the next release of Bond.

Where Next?

Popular in Announcing Top

OvermindDL1
Been making an MLElixir thing (not released yet…) for fun in spare time in the past day. I’m just trying to see how much I can get an ML...
132 14410 106
New
kelvinst
Hey everyone! Well, we made this lib a while ago and now we decided to finally go out and public with it! It’s a tool for creating and m...
New
josevalim
Yes, yet another parser combinator library! Most of the parser combinators in the ecosystem are either compile-time, often using AST tra...
159 19951 141
New
mindok
What is ContEx? A pure Elixir server-side data plotting/charting library outputting SVG. It has nice barcharts in particular and works g...
New
maltoe
Hello! Came here to announce ChromicPDF, a pet project PDF generator I’ve been working on for the past few months. Why another PDF gener...
New
oltarasenko
Dear Elixir community, After a year of development, bug fixes, and improvements, we are proudly ready to share the release of Crawly 0.1...
New
martinthenth
Hello everybody :wave: Recently, some of my colleagues talked about database ids and uuids and their problems, and I remembered the pain...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31586 112
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement