On Tue, Jun 2, 2015 at 11:24 AM, Jonathan S. Shapiro <s...@eros-os.org> wrote:
> The other day, somebody (Keean?) mentioned refinement types, which prompted
> me to go look at Liquid Haskell once again.
>
> I have mixed feelings about things like this. They add some truly remarkable
> expressive power to a language - most notably in the area of proving
> exception freedom. That's very, very useful, and I'm very much in favor of
> that idea. The SPARC/Ada system gets a lot of value out of this. But i see
> three issues with them, which are basically the same issues that I have with
> dependent types.
>
> The first issue is that things like refinement types force us into adopting
> some form of prover. The problem with this is that we don't really know how
> to specify the *behavior* of a prover, so if we add such a dependency we
> lose the ability to know whether a given [valid] program input to the
> compiler will reliably compile in all compiler implementations. The best
> solution I have for this comes in two parts:
>
> 1. Provide a baseline solver as part of the compiler implementation, whose
> presence all implementations can rely on. This guarantees that many
> verification conditions can be repeatably discharged in all implementations.
> 2. Provide a means (a sub-language) for articulating a proof by hand, such
> that the prove can be checked rather than generated by the compiler.

Another, more experimental option is what I'm trying to do, where the
core language only has manual proofs, but also a practical form of
proof by reflection that allows libraries to safely add provers and
custom checkers.

With this approach, the core language can punt on how programmers will
actually go about solving proof obligations. And since that's what
I've been planning to do, I don't know any good ideas for
standardizing provers.

> This
> gets us into questions about proof languages and logics that are more than I
> want to bite off at this time.

If you ever decide to bite off the problem of designing a logic and
proof language, it's something I know quite a lot about.

> The second issue is evolvability. Suppose we implement (e.g.) a certain SMT
> solver in BitC v2. Later, in BitC v3, we decide to improve the SMT solver.
> How do we make sure that existing programs which rely on the v2 solver still
> compile in v3? This is really the solver specification in disguise,
> appearing in a new place.

Yeah. Basically, I don't know. But in the case of a SMT solver for
general purpose stuff, (no language-specific theories) it seems like a
better prover would be able to handle all cases of an older prover.
That's a guess, based on the hope that general-purpose SMT solvers are
sufficiently well-understood. If the guess is right, you might not
have to worry about it.

I don't know much about automated provers. Are there results about
provers that have a known lower bound on the set of problems they can
solve? This sort of thing seems like something a lot of people would
want, like you, since that lower bound would be something you could
require in a language spec.

> My final (third) concern is that proof discharge does not always play nicely
> with module boundaries. A discharge *here* may require intimate knowledge of
> code behavior *there*.

My point of view is that there is nothing truly new going on with
modularity, just because of logic and proofs. If correctness of A
depends on a lot of implementation details of B, then there shouldn't
really be an abstraction boundary between A and B. With weak types,
you can maybe convince yourself that they can be separated. But if
those correctness dependencies were real, then you can't actually
modify A and B independently, and expect the combination to still
work.

In other words, I see the new apparent issues with modularity as just
uncovering issues that were really there all along, but not
acknowledged by the language. A well-designed system will be modular,
no matter how strong the types are. (That can be read either as a
strict stance on "well-designed" systems, or an optimistic prediction
about systems that would already be considered modular. It's intended
mostly as the latter, but I'm willing to fall back to the former.)

> Which raises several concerns:
>
>  - We're now engaged in whole-program proof

What do you mean? Correctness properties are part of a module
interface. Proofs in one module use properties exported by other
modules. It seems OK to me.

>  - The properties we rely on may not be part of the guarantee at the module
> interface

If you're saying you'd allow a proof to use a property (of code in
another module) that isn't part of the module interface, then you
_are_ now engaged in whole-program proof, and so you probably
shouldn't allow that.

I just noticed that--although when programmers talk about modularity,
I think we're talking about encapsulation and abstraction--not all
modules are opaque. So then maybe all I need to point out is that you
can compose a program from translucent modules, and then proofs in one
module see the implementation of the other module. But for a large
program you'll need abstraction boundaries, so...

Maybe what you're getting at is the need for programmers to anticipate
the necessary properties for a an abstraction. I see this too as just
uncovering a real problem: if you make an abstraction boundary without
knowing what properties are really depended upon, then the abstraction
was premature. Mathematically speaking, an abstraction should tell you
the set of correct implementations.

A lot of abstractions in programming are just to allow implementation
freedom, and the correctness property is "Works like this naive
implementation. (But faster.)".

>  - The properties we *need* at the interface tend to grow without textually
> reasonable bound.

Yes. You shouldn't expect to infer them from the code, if that's what
you're getting at. Programs with strong properties tend to depend also
on libraries of mathematical definitions, which makes writing strong
properties feasible. Facts about these definitions get proven, so as
to be used in program proofs. Programming environments become proof
assistants.

> This becomes a particular concern as discharge obligations become embedded
> in standard libraries.

Hmm. How would that happen? The ways I know of, the obligation would
either be solved in the library that causes it, or it gets
rediscovered by each user. Either way, you don't end up storing
internal obligations in the module interface.

> If these issues aren't real in practice, then it's definitely worth having a
> more serious discussion about all this.

I'm afraid the only real issue is that the maximum expressiveness that
refinement types can get in general is squarely at odds with the kind
of complete, automatic solution you want for BitC.

I don't know about what kinds of special cases of refinement types
have complete, automatic solutions. (Sorry if that's what you were
asking about all along, and I just wasted our time.)

> It may well be that properties tied
> to types are cleaner in these regards than properties more generally.

I don't know what you mean by "properties tied to types". Aren't all
properties tied to the type of object they are properties of?

> So: I really like the idea, but I'm not convinced it should be incorporated
> into BitC. A middle position would be to leave a placeholder syntax for it
> and do this type of discharge using an external tool for those programs that
> need it.

Maybe it'd be better to let the hypothetical future refinement type
system designer design the syntax too. They hypothetically ought to
know what they're doing better than us.
_______________________________________________
bitc-dev mailing list
bitc-dev@coyotos.org
http://www.coyotos.org/mailman/listinfo/bitc-dev

Reply via email to