If a monad is implemented in terms of monad transformers that are provided by the Lean standard library, such as ExceptT and StateT, then it should not require additional instances.
Other monads will require instances of WP, LawfulMonad, and WPMonad.
The tactic has been designed to support monads that model single-threaded control with state that might be interrupted; in other words, the effects that are present in ordinary imperative programming.
More exotic effects have not yet been investigated.
Once the basic instances are provided, the next step is to prove an adequacy lemma.
This lemma should show that the weakest precondition for running the monadic computation and asserting a desired predicate is in fact sufficient to prove the predicate.
In addition to the definition of the monad, typical libraries provide a set of primitive operators.
Each of these should be provided with a specification lemma.
It may additionally be useful to make the internals of the state private, and export a carefully-designed set of assertion operators.
The specification lemmas for the library's primitive operators should ideally be precise specifications of the operators as predicate transformers.
While it's often easier to think in terms of how the operator transforms an input state into an output state, verification condition generation will work more reliably when postconditions are completely free.
This allows automation to instantiate the postcondition with the exact precondition of the next statement, rather than needing to show an entailment.
In other words, specifications that specify the precondition as a function of the postcondition work better in practice than specifications that merely relate the pre- and postconditions.
Schematic Postconditions
The function double doubles a natural number state:
Thinking chronologically, a reasonable specification is that value of the output state is twice that of the input state.
This is expressed using a schematic variable that stands for the initial state:
However, an equivalent specification that treats the postcondition schematically will lead to smaller verification conditions when double is used in other functions:
The first projection of the postcondition is its stateful assertion.
Now, the precondition merely states that the postcondition should hold for double the initial state.
Rather than writing it from scratch, the WP instance uses PredTrans.pushArg.
This operator was designed to model state monads, but LogM can be seen as a state monad that can only append to the state.
This appending is visible in the body of the instance, where the initial state and the log that resulted from the action are appended:
The adequacy lemma has one important detail: the result of the weakest precondition transformation is applied to the empty array.
This is necessary because the logging computation has been modeled as an append-only state, so there must be some initial state.
Semantically, the empty array is the correct choice so as to not place items in a log that don't come from the program; technically, it must also be a value that commutes with the append operator on arrays.
Next, each operator in the library should be provided with a specification lemma.
There is only one: log.
For new monads, these proofs must often break the abstraction boundaries of Hoare triples and weakest preconditions; the specifications that they provide can then be used abstractly by clients of the library.