I'm writing a DbC (Design by Contract) library for Nim and I would like you to
give me your thoughts on this topic (references at the end).
I wanted to get into Nim's code transformations so I thought it would be a good
idea to try implementing some simple libraries. Actually, this one was ready in
the present form more than two months ago but I didn't feel like publishing it
yet as it lacked crucial (or so I thought) features like, let's say,
inheritance. Due to lack of time (and being lazy).
# Compiler extensions / module-range macros
As far as I know, Nim deserves a name of "extremely statically compiled
language", one of the reasons being advanced compile-time checks (like
overloading for _static_ arguments or _noSideEffect_ pragma). It sounds
reasonable to extend it with a prover. I thought it would be nice to use it
just like any other nimble package. Unluckily just importing a package can't
(as far as I know) enable any code transformation for the module (unlike Rust
plugins) but wrapping the code doesn't sound all that bad. Maybe anyone here
have better ideas for making an easy-to-install compiler extension?
# Exceptions
I'm not sure if exception-aware contracts would be a good thing to do. Of
course they would complicate proving process but if any exception possible to
throw would need to be included in contract signature so that the prover knows
what conditions are fulfilled if the exception is raised (or even why it could
be thrown), it could probably help using formal contracts in error-prone
environments.
Why even bother? There are cases when we can't guarantee that the operation
will be finished successfully but we can, let's say, promise that even if it
doesn't, it won't corrupt it's arguments. Like impossible swap will just leave
it's arguments as they are.
# Type invariant inheritance
I have seen at least three approaches to type invariant (single) inheritance
none of which sounds perfect. Some reasonable contracts are difficult to write
down using variable-oriented type invariants but using quantifiers, while
handy, is troublesome in implementation if it's bound not to be ridiculously
inefficient.
It seems to me that UML-like contracts could be a good idea but... they only
handle very specific traits (e.g. _add-only_, _unique_, _sorted-by_). It could
be also nice to introduce analogical traits for subroutines, e.g. _commutative_
or _associativeWith_. Maybe custom contractual traits declarations would do?
What grammar should contractual type invariants (or type traits) use? Nim's
grammar seems a bit limited here (especially lack of macro-pragma for fields)
and macros have to accept parsable Nim. I considered a few possibilities:
* mock field – objects-only; as the contract's body is parsed as the field's
type, no more than one expression is accepted
type parityPair = object of RootObj
a: int
b: int
invariant:
a + b mod 2 == 0
* invariant subroutine – less compact, doesn't seem like a part of the
language; using the type before declaring invariant possible
invariant(p: parityPair) =
p.a + p.b mod 2 == 0
anotherRule(p.a, p.b)
* new type declaration block – seems less natural
typeWithInvariant parityPair of RootObj:
a: int
b: int
invariant:
a+b mod 2 == 0
anotherRule(a, b)
Of course there are other details like how to express 'previous iteration'
concept gracefully and many more.
Glad for any help.
Basics:
[https://en.wikipedia.org/wiki/Design_by_contract](https://en.wikipedia.org/wiki/Design_by_contract)
My library on github:
[https://github.com/Udiknedormin/NimContracts](https://github.com/Udiknedormin/NimContracts)