I'm looking for advice on beginning the implementation of a contract system (i.e. not in Racket).
I've been using and contributing to Guix, an unprivileged "purely functional" package manager (and full GNU/Linux distribution) implemented in Guile.
Many of Guix's functions and data structures don't check that their arguments meet their implicit contracts, leading to confusing error messages when mistakes are made. Recently, one of the Guix committers brought up this issue and suggested that Guix ought to adopt a mechanism for more conveniently checking arguments—which sounded a lot like contracts. In the subsequent discussion, it emerged that several Guix developers were, in fact, interested in contracts, but I guess the prospect of going from 0 to contracts seemed like a bit of an intimidating barrier to entry.
I tried to sketch out a minimal API for contracts (I'll quote most of that message at the end of this for convenience), and I was encouraged to put together a prototype implementation, but, as I wrote in that message,
So here I am doing that!
I've read several of the contract papers and watched "Inside Racket Seminar: Robby Findler on Contracts" (though not recently), and I think I have at least a reasonable understanding of how the contract system is implemented generally, though, again, I have only peripherally contributed to the implementation of racket/contract
.
Broadly, I'm interested in the questions I wouldn't think to ask.
More specifically, for this to be viable for Guix, I think the key challenge is to find a "minimum viable product" that:
- is immediately useful;
- has as little overhead as possible; and
- provides a foundation to grow more complete by the "infectious" process Robby has described by
- avoiding design decisions that would create problems later.
On the last point, this library would be internal to Guix for the forseeable future, so it has much weaker compatibility requirements than racket/contract
—but, on the other hand, Guix has on the order of 800,000 lines of Scheme.
As far as minimization, one question seems to be what kinds of unsoundness can be lived with to start without causing big problems later. We would have no hope of being a "complete monitor" for the foreseeable future, nor would we have impersonators/chaperones for Scheme data in general (something for Guix record types might be possible), and Guile has mutable pairs, which I'm fairly convinced, in the absence of chaperones, will ultimately have to retain the "just say no to set-c{a,d}r!
" status quo. I know racket/contract
has some unsound contracts (e.g. box/c
with #:flat? #t
applied to a mutable box), so it seems like a workable pragmatic compromise. On the other hand, there's unsoundness that, with the benefit of hindsight, may be avoidable, like the issue with mutable (byte)strings.
Similarly, I wouldn't even be the right person to attempt any Guile-specific performance tuning, but hard-learned lessons about performance in general would certainly be valuable.
Finally, as promised, here are some more of my earlier thoughts:
[…]