DesignByContract.jl: DbC interface for Julia (feedback)

I’ve been working on a new package for Julia which brings the “Design by Contract” functionality from Eiffel in the form of macros. I think this is something that could be used on future Julia application development, when considering the correctness checks on runtime as well as easing the testing process. However, developing a package that is basically adding checks embedded within Julia code seems a bit off since the language thrives on efficiency.

I wanted to know the community’s thoughts on this as well as ask for some feedback on the code and organization. I’m not exactly an experienced Julia developer, so every idea/complaint would be help me learn :grin:

https://gitlab.com/ghaetinger/DesignByContract-jl

using DesignByContract

maxDictSize = 2

# Function for adding a variable to a string-key dictionary with a non-null key
@contract begin
    require(length(dict) < maxDictSize, length(key) > 0)
    function putItem!(dict :: Dict{String, Any}, key :: String, item)
        dict[key] = item
        return nothing
    end
end

fruits = Dict{String, Any}()

putItem!(fruits, "apple", :red)
putItem!(fruits, "blueberry", :blue)
putItem!(fruits, "", :purple)
..
Breach on Requirement Expression 'length(dict) < maxDictSize' in function 'putItem!'
7 Likes

You may want to add an example here, I wasn’t familiar with this and ended up reading your entire README (which is quite nice btw) to get an idea of what the goal is :see_no_evil:

I’m a bit unclear of what’s the difference between this and using a bunch of assert statements?

2 Likes

Oh. OK, I’ll add a simple example. The idea is to make the code more readable, testable and more reliable by explicitly documenting the method’s responsibilities. You can read more on it here:

https://www.eiffel.com/values/design-by-contract/introduction/

I honestly did this because I wanted to understand how to program macros and ended up with some functional code :laughing: But yeah, it’s just basically making a bunch of if statements readable.

1 Like

The link to the repository has been moved to Github but I can’t seem to edit it. So here it is:

https://github.com/GuilhermeHaetinger/DesignByContract.jl

2 Likes

If I understand it correctly, this is a way to have nicer syntax for pre and postconditions. In general, I’m quite a fan of these formalisms to improve code quality. I’ve mainly seen them in Java before and it was aimed at huge software projects with business logic, there you don’t mind about trading performance for correctness.

In Julia, I think, you would have to find your user group and try to optimise your package for them. A big benefit of Julia is that functions are generic (ducktyped) and can suprisingly often be used for things the original writer never thought of. Making the preconditions too strict will hinder this. Restricting on types is already frowned upon by some, they call it an anti-pattern.

1 Like

On second thought, why not leverage Julia’s typesystem? In essence, pre- and postconditions are just types. For example, consider Julia’s Vector type. If we want to define a precondition on this vector that it is sorted, then that could basically be expressed as a SortedVector type. So, in code

min(x::SortedVector) = first(x)

The difficulty here is to make a datastructure that enforces the conditions. Probably, this should be some kind of immutable datastructure.

I’m not very familiar with Julia’s typesystem, so maybe these things are impossible. In types, your example dictionary type could be something like BoundedDict{1, maxDictSize). A neat package would allow the user to easily define combinations of types like ComposedType{NonEmptyDict, UpperBoundedDict{maxDictSize}.

EDIT: Note that via types, you can actually improve performance like with the min function defined above. An earlier discussion here about sorted lists (Sorted Array implementation? - #2 by garrison). There is a pointer to the implementation of SortedSet.

1 Like

You are perfectly aligned with the purpose of the package. Although it was definitely more of a proof of concept/Julia practice for me, I feel like it could come to use by someone who’s using Julia to design a Microservices platform (like this) or anything that relates to production code and requires robustness. These projects might be willing to sacrifice a bit of performance for correctness. This is just probably what I would use when making something with Genie.jl, for example. I still think this might not be of use to most Julia users (it’s not that much use to me too because I use Julia for image processing research :laughing:).

This is a great idea. It would definitely make DBC more efficient. I’m also not an expert in the Julia typesystem, but I guess I could take a look at it. Thank you for the suggestion!

1 Like

Having the require clause outside of the function would worry me a bit. It does refer to things which are defined as arguments of the function, so wouldn’t the natural place for it be as the first line of the function definition?

2 Likes

That’s true. Good luck :ok_hand:

1 Like

I see. That’s a valid point. Since the @contract macro basically pattern matches the presence of a requirement call and moves the checks to the first line of the function, this position doesn’t really make a difference for the functionality, but for the syntax sugar structure. I tried to mimic the looks of Java’s package ïContract", with which we define both the preconditions and the postconditions inside a function’s javadoc:

/** 
 * @pre f >= 0.0
 * @post Math.abs((return * return) - f) < 0.001 
 */ 
public float sqrt(float f) { ... } 

However, in Eiffel, the original DBC language, we get the conditions inside the function:

put (x: ELEMENT; key: STRING) is
		-- Insert x so that it will be retrievable through key.
	require
		count <= capacity
		not key.empty
	do
		... Some insertion algorithm ...
	ensure
		has (x)
		item (key) = x 
		count = old count + 1
	end

I even think that Eiffel’s syntax might be the best one to use, since the positions of the requirement and ensure calls don’t need their check position searched. I’ll be looking into this and @rikh’s comments to define a better structure aiming a higher efficiency. Thank you for your suggestion :smiley:

1 Like