Tutorial

Invariants.jl gives you the tools to define and compose invariants to give more helpful error messages to package users. This page gives an overview of the different features of invariants.

Defining and checking invariants

An invariant is a function that takes an input and either returns an error message if the input does not satisfy the invariant or nothing if it does. We use the invariant function to create them. Let's create an invariant that checks whether a number is positive:


			
			
			
			
			using
			
			
			 
			
	
		
			Invariants
			
			:
			
			
			 
			
	
		
			Invariants
			
			,
			
			
			 
			
	
		
			invariant
			
			,
			
			
			 
			
	
		
			check
			
			

			
			
			inv
			
			 
			
			=
			
			 
			
			
			
	
		
			invariant
			
			(
			
			"
			
			Is positive
			
			"
			
			)
			
			 
			
			do
			
			
			
			 
			
			n
			
			
			
    
			
			
			
			n
			
			 
			
			>
			
			 
			
			0
			
			 
			
			?
			
			 
			
			nothing
			
			 
			
			:
			
			 
			
			"
			
			n should be a positive number!
			
			"
			
			

			
			end

			Invariant("Is positive")

We can then check whether the invariant holds for an input using check :


			
			
			
			
			using
			
			
			 
			
	
		
			Invariants
			
			:
			
			
			 
			
	
		
			check
			
			

			
			
	
		
			check
			
			(
			
			inv
			
			,
			
			 
			
			1
			
			)

			✔ Invariant satisfied: Is positive

			
			
			
	
		
			check
			
			(
			
			inv
			
			,
			
			 
			
			-1
			
			)

			⨯ Invariant not satisfied: Is positive

n should be a positive number!
Checking invariants

While check gives you a nicely formatted report, you can also use check_bool to get a Boolean value or check_throw to throw an error when an invariant is not satisfied.

Combining invariants

In real use cases, many invariants can be logically decomposed into smaller invariants. For example, interfaces often require a data structure to implement multiple methods.

We can logically compose invariants with the invariant(title, [inv1, inv2, ...]) method. Let's take the hasmethod_invariant helper introduced on the Examples page and build an invariant that checks that a data structure supports indexing and is non-empty.


			
			
			
			inv_indexing
			
			 
			
			=
			
			 
			
			
	
		
			invariant
			
			(
			
			
    
			
			"
			
			`xs` is indexable
			
			"
			
			,
			
			
    
			
			
			[
			
			
        
			
			
			
	
		
			Invariants
			
			.
			
			
	
		
			hasmethod_invariant
			
			(
			
			length
			
			,
			
			 
			
			
			:
			
			xs
			
			)
			
			,
			
			
        
			
			
			
	
		
			invariant
			
			(
			
			"
			
			`xs is not empty`
			
			"
			
			)
			
			 
			
			do
			
			
			
			 
			
			
			(
			
			
			;
			
			 
			
			xs
			
			)
			
			
			
            
			
			
			
			isempty
			
			(
			
			xs
			
			)
			
			 
			
			?
			
			 
			
			"
			
			`xs` is empty!
			
			"
			
			 
			
			:
			
			 
			
			nothing
			
			
        
			
			end
			
			,
			
			
        
			
			
			
	
		
			Invariants
			
			.
			
			
	
		
			hasmethod_invariant
			
			(
			
			
			Base
			
			.
			
			
			getindex
			
			,
			
			 
			
			
			:
			
			xs
			
			,
			
			 
			
			
			
			:
			
			idx
			
			 
			
			=>
			
			 
			
			1
			
			)
			
			,
			
			
    
			
			]
			
			,
			
			
    
			
			
			inputfn
			
			 
			
			=
			
			 
			
			
			xs
			
			 
			
			->
			
			 
			
			
			(
			
			
			;
			
			 
			
			xs
			
			)
			
			

			
			)
			
			

			
			
	
		
			check
			
			(
			
			inv_indexing
			
			,
			
			 
			
			
			[
			
			1
			
			,
			
			 
			
			2
			
			,
			
			 
			
			3
			
			]
			
			)

			✔ Invariant satisfied: `xs` is indexable

This has two advantages:

  • We can reuse previously defined invariants

  • We get specific error messages based on which constituent invariants were not satisfied

Let's see the second point in action, by passing in inputs that violate different invariants.


			
			
			
	
		
			check
			
			(
			
			inv_indexing
			
			,
			
			 
			
			nothing
			
			)

			⨯ Invariant not satisfied: `xs` is indexable
All invariants listed below should be satisfied:

  Method Base.length(xs) implemented

    When calling length, got a MethodError. This means that there is no method
    implemented for the given arguments. To fix this, please implement the
    following method:
    
        Base.length(xs::Nothing)
    
? xs is not empty
? Method Base.getindex(xs, idx) implemented

			
			
			
	
		
			check
			
			(
			
			inv_indexing
			
			,
			
			 
			
			
			[
			
			]
			
			)

			⨯ Invariant not satisfied: `xs` is indexable
All invariants listed below should be satisfied:

  Method Base.length(xs) implemented
 xs is not empty

    `xs` is empty!
    
? Method Base.getindex(xs, idx) implemented

			
			
			
	
		
			check
			
			(
			
			inv_indexing
			
			,
			
			 
			
			
			[
			
			1
			
			,
			
			 
			
			2
			
			,
			
			 
			
			3
			
			]
			
			)

			✔ Invariant satisfied: `xs` is indexable