Related
There is a programming "style" (or maybe paradigm, i'm not sure what to call it) which is as follows:
First, you write a specification: a formal description of what your (whole, or part of) program is to do. This is done within the programming system; it is not a separate artifact.
Then, you write the program, but - and this is the key distinction between this programming style and others - every step of this writing task is guided in some way by the specification you've written in the previous step. How exactly this guidance happens varies wildly; in Coq you have a metaprogramming language (Ltac) which lets you "refine" the specification while building the actual program behind the scenes, whereas in Agda you compose a program by filling "holes" (i'm not actually sure how it goes in Agda, as i'm mostly used to Coq).
This isn't exactly everyone's favorite style of programming, but i'd like to try practicing it in general-purpose, popular programming languages. At least in Coq i've found it to be fairly addictive!
...but how would i even search for ways to do it outside proof assistants? Which leads us to the question: I'm looking for a name for this programming style, so that i can try looking up tools that let me program like that in other programming languages.
Mind you, of course a more proper question would be directly asking for examples of such tools, but AFAIK questions asking for lists of answers aren't appropriate for Stack Exchange sites.
And to be clear, i'm not all that hopeful i'm really going to find much; these are mostly academic pastimes, and your typical programming language isn't really amenable to this style of programming (for example, the specification language might end up being impossibly complex). But it's worth a shot!
It is called proof-driven development (or type-driven development). However, there is very little information about it.
This process you mention about slowly creating your program by means of ltac (in the case of coq) or holes (in the case of Agda and Idris) is called refinement. So you will also find reference in the literature for this style as proof by refinement or programming by refinement.
Now the most important thing to realize is that this style of programming is intrinsic to more complex type system that will allow you to extract as much information as possible the current environment. So it is natural to find attached with dependent types, although it is not necessarily the case.
As mentioned in another response you're also going to find references to it as Type-Driven Development, there is an idris book about it.
You may be interested in looking into some other projects such as Lean, Isabelle, Idris, Agda, Cedille, and maybe Liquid Haskell, TLA+ and SAW.
As pointed out by the two previous answers, a possible name for the program style you mention certainly is: type-driven development.
From the Coq viewpoint, you might be interested in the following two references:
Certified Programming with Dependent Types (CPDT, by Adam Chlipala): a Coq textbook that teaches advanced techniques to develop dependently-typed Coq theories and automate related proofs.
Experience Report: Type-Driven Development of Certified Tree Algorithms in Coq (by Reynald Affeldt, Jacques Garrigue, Xuanrui Qi, Kazunari Tanaka), published at the Coq Workshop 2019 (slides, extended abstract):
The authors also use the acronym TDD, which interestingly enough, also has another acceptation in the software engineering community: test-driven development (this widely used methodology naturally leads to high-quality test suites).
Actually, both acceptations of TDD share a common idea: one systematically starts by writing the specification (of the considered unit), then only after that, writing some code that fulfills the spec (make the unit tests pass), then we loop and incrementally specify+implement(+refactor) other code units.
Last but not least, there are some extra pointers in this discussion from the Discourse OCaml forum.
I used FEST-Assert and moved to AssertJ after it stopped development.
Recently I was pointed to Google repository with another assertions library Truth (http://google.github.io/truth/).
Reading the examples I can not find any advantage of start using it over AssertJ. So it is just matter of taste what to use. But maybe I missed the point, did I?
From one of their comments at GitHub:
The core difference is that the design of Truth includes two specific
areas of extensibility - that of a strategy for proposition failure -
such that a "subject" for Integers, or a subject for Strings can be
re-used in the context of completely different results for failure. A
notable example is the distinction between JUnit's use of
AssertionError and it's AssumptionViolationException. Truth lets you
use the same proposition classes for both.
The other area of flexibility is the ability to create new
assertion/proposition types and hook them in without declaring
possibly conflicting static methods to import. This can be for new
types (say, adding protobufs) or for new uses of existing types (say,
Strings that are treated as Uris). This is the assertAbout() feature.
Other than that, Truth is very similar to AssertJ, since it was
inspired by FEST, of which AssertJ is a fork of the 2.0 development
line.
To sum up, Truth is designed to be a bit more extensible and flexible, but AssertJ will be great (possibly the greatest) for assertions on standard types.
I see a couple of different research groups, and at least one book, that talk about using Coq for designing certified programs. Is there are consensus on what the definition of certified program is? From what I can tell, all it really means is that the program was proved total and type correct. Now, the program's type may be something really exotic such as a list with a proof that it's nonempty, sorted, with all elements >= 5, etc. However, ultimately, is a certified program just one that Coq shows is total and type safe, where all the interesting questions boil down to what was included in the final type?
Edit 1
Based on wjedynak's answer, I had a look at Xavier Leroy's paper "Formal Verification of a Realistic Compiler", which is linked in the answers below. I think this contains some good information, but I think the more informative information in this sequence of research can be found in the paper Mechanized Semantics for the Clight Subset of the C Language by Sandrine Blazy and Xavier Leroy. This is the language that the "Formal Verification" paper adds optimizations to. In it, Blazy and Leroy present the syntax and semantics of the Clight language and then discuss the validation of these semantics in section 5. In section 5, there's a list of different strategies used for validating the compiler, which in some sense provides an overview of different strategies for creating a certified program. These are:
Manual reviews
Proving properties of the semantics
Verified translations
Testing executable semantics
Equivalence with alternate semantics
In any case, there are probably points that could be added and I'd certainly like to hear about more.
Going back to my original question of what the definition is of a certified program, it's still a little unclear to me. Wjedynak sort of provides an answer, but really the work by Leroy involved creating a compiler in Coq and then, in some sense, certifying it. In theory, it makes it possible to now prove things about the C programs since we can now go C->Coq->proof. In that sense, it seems like there's this work flow where we could
Write a program in X language
Form of a model of the program from step 1 in Coq or some other proof assistant tool. This could involve creating a model of the programming language in Coq or it could involve creating a model of the program directly (i.e. rewriting the program itself in Coq).
Prove some property about the model. Maybe it's a proof about the values. Maybe it's the proof of the equivalence of statements (stuff like 3=1+2 or f(x,y)=f(y,x), whatever.)
Then, based on these proofs, call the original program certified.
Alternatively, we could create a specification of a program in a proof assistant tool and then prove properties about the specification, but not the program itself.
In any case, I'm still interested in hearing alternative definitions if anyone has them.
I agree that the notion seems vague, but in my understanding a certified program is a program equipped/together with the proof of correctness. Now, by using rich and expressive type signatures you can make it so there is no need for a separate proof, but this is often only a matter of convenience. The real issue is what do we mean by correctness: this a matter of specification. You can take a look at e.g. Xavier Leroy. Formal verification of a realistic compiler.
First note that the phrase "certified" has a slightly French bias: elsewhere the expression "verified" or "proven" is often used.
In any case it is important to ask what that actually means. X. Leroy and CompCert is a very good starting point: it is a big project about C compiler verification, and Leroy is always keen to explain to his audience why verification matters. Especially when talking to people from "certification agencies" who usually mean testing, not proving.
Another big verification project is L4.verified which uses Isabelle/HOL. This part of the exposition explains a bit what is actually stated and proven, and what are the consequences. Unfortunately, the actual proof is top secret, so it cannot be checked publicly.
A certified program is a program that is paired with a proof that the program satisfies its specification, i.e., a certificate. The key is that there exists a proof object that can be checked independently of the tool that produced the proof.
A verified program has undergone verification, which in this context may typically mean that its specification has been formalized and proven correct in a system like Coq, but the proof is not necessarily certified by an external tool.
This distinction is well attested in the scientific literature and is not specific to Francophones. Xavier Leroy describes it very clearly in Section 2.2 of A formally verified compiler back-end.
My understanding is that "certified" in this sense is, as Makarius pointed out, an English word chosen by Francophones where native speakers might instead have used "formally verified". Coq was developed in France, and has many Francophone developers and users.
As to what "formal verification" means, Wikipedia notes (license: CC BY-SA 3.0) that it:
is the act of proving ... the correctness of intended algorithms underlying a system with respect to a certain formal specification or property, using formal methods of mathematics.
(I realise you would like a much more precise definition than this. I hope to update this answer in future, if I find one.)
Wikipedia especially notes the difference between verification and validation:
Validation: "Are we trying to make the right thing?", i.e., is the product specified to the user's actual needs?
Verification: "Have we made what we were trying to make?", i.e., does the product conform to the specifications?
The landmark paper seL4: Formal Verification of an OS Kernel (Klein, et al., 2009) corroborates this interpretation:
A cynic might say that an implementation proof only shows that the
implementation has precisely the same bugs that the specification
contains. This is true: the proof does not guarantee that the
specification describes the behaviour the user expects. The
difference [in a verified approach compared to a non-verified one]
is the degree of abstraction and the absence of whole classes of bugs.
Which classes of bugs are those? The Agda tutorial gives some idea:
no runtime errors (inevitable errors like I/O errors are handled; others are excluded by design).
no non-productive infinite loops.
It may means free of runtime error (numeric overflow, invalid references …), which is already good compared to most developed software, while still weak. The other meaning is proved to be correct according to a domain formalization; that is, it does not only have to be formally free of runtime errors, it also has to be proved to do what it's expected to do (which must have been precisely defined).
Scheme offers a primitive call-with-current-continuation, commonly abbreviated call/cc, which has no equivalent in the ANSI Common Lisp specification (although there are some libraries that try to implement them).
Does anybody know the reason why the decision of not creating a similar primitive in the ANSI Common Lisp specification was made?
Common Lisp has a detailed file compilation model as part of the standard language. The model supports compiling the program to object files in one environment, and loading them into an image in another environment. There is nothing comparable in Scheme. No eval-when, or compile-file, load-time-value or concepts like what is an externalizable object, how semantics in compiled code must agree with interpreted code. Lisp has a way to have functions inlined or not to have them inlined, and so basically you control with great precision what happens when a compiled module is re-loaded.
By contrast, until a recent revision of the Scheme report, the Scheme language was completely silent on the topic of how a Scheme program is broken into multiple files. No functions or macros were provided for this. Look at R5RS, under 6.6.4 System Interface. All that you have there is a very loosely defined load function:
optional procedure: (load filename)
Filename should be a string naming an existing file containing Scheme source code. The load procedure reads expressions and definitions from the file and evaluates them sequentially. It is unspecified whether the results of the expressions are printed. The load procedure does not affect the values returned by current-input-port and current-output-port. Load returns an unspecified value.
Rationale: For portability, load must operate on source files. Its operation on other kinds of files necessarily varies among implementations.
So if that is the extent of your vision about how applications are built from modules, and all details beyond that are left to implementors to work out, of course the sky is the limit regarding inventing programming language semantics. Note in part the Rationale part: if load is defined as operating on source files (with all else being a bonus courtesy of the implementors) then it is nothing more than a textual inclusion mechanism like #include in the C language, and so the Scheme application is really just one body of text that is physically spread into multiple text files pulled together by load.
If you're thinking about adding any feature to Common Lisp, you have to think about how it fits into its detailed dynamic loading and compilation model, while preserving the good performance that users expect.
If the feature you're thinking of requires global, whole-program optimization (whereby the system needs to see the structural source code of everything) in order that users' programs not run poorly (and in particular programs which don't use that feature) then it won't really fly.
Specifically with regard to the semantics of continuations, there are issues. In the usual semantics of a block scope, once we leave a scope and perform cleanup, that is gone; we cannot go back to that scope in time and resume the computation. Common Lisp is ordinary in that way. We have the unwind-protect construct which performs unconditional cleanup actions when a scope terminates. This is the basis for features like with-open-file which provides an open file handle object to a block scope and ensures that this is closed no matter how the block scope terminates. If a continuation escapes from that scope, that continuation no longer has a valid file. We cannot simply not close the file when we leave the scope because there is no assurance that the continuation will ever be used; that is to say, we have to assume that the scope is in fact being abandoned forever and clean up the resource in a timely way. The band-aid solution for this kind of problem is dynamic-wind, which lets us add handlers on entry and exit to a block scope. Thus we can re-open the file when the block is restarted by a continuation. And not only re-open it, but actually position the stream at exactly the same position in the file and so on. If the stream was half way through decoding some UTF-8 character, we must put it into the same state. So if Lisp got continuations, either they would be broken by various with- constructs that perform cleanup (poor integration) or else those constructs would have to acquire much more hairy semantics.
There are alternatives to continuations. Some uses of continuations are non-essential. Essentially the same code organization can be obtained with closures or restarts. Also, there is a powerful language/operating-system construct that can compete with the continuation: namely, the thread. While continuations have aspects that are not modeled nicely by threads (and not to mention that they do not introduce deadlocks and race conditions into the code) they also have disadvantages compared to threads: like the lack of actual concurrency for utilization of multiple processors, or prioritization. Many problems expressible with continuations can be expressed with threads almost as easily. For instance, continuations let us write a recursive-descent parser which looks like a stream-like object which just returns progressive results as it parses. The code is actually a recursive descent parser and not a state machine which simulates one. Threads let us do the same thing: we can put the parser into a thread wrapped in an "active object", which has some "get next thing" method that pulls stuff from a queue. As the thread parsers, instead of returning a continuation, it just throws objects into a queue (and possibly blocks for some other thread to remove them). Continuation of execution is provided by resuming that thread; its thread context is the continuation. Not all threading models suffer from race conditions (as much); there is for instance cooperative threading, under which one thread runs at a time, and thread switches only potentially take place when a thread makes an explicit call into the threading kernel. Major Common Lisp implementations have had light-weight threads (typically called "processes") for decades, and have gradually moved toward more sophisticated threading with multiprocessing support. The support for threads lessens the need for continuations, and is a greater implementation priority because language run-times without thread support are at technological disadvantage: inability to take full advantage of the hardware resources.
This is what Kent M. Pitman, one of the designers of Common Lisp, had to say on the topic: from comp.lang.lisp
The design of Scheme was based on using function calls to replace most common control structures. This is why Scheme requires tail-call elimination: it allows a loop to be converted to a recursive call without potentially running out of stack space. And the underlying approach of this is continuation-passing style.
Common Lisp is more practical and less pedagogic. It doesn't dictate implementation strategies, and continuations are not required to implement it.
Common Lisp is the result of a standardization effort on several flavors of practical (applied) Lisps (thus "Common"). CL is geared towards real life applications, thus it has more "specific" features (like handler-bind) instead of call/cc.
Scheme was designed as small clean language for teaching CS, so it has the fundamental call/cc which can be used to implement other tools.
See also Can call-with-current-continuation be implemented only with lambdas and closures?
With the growth of dynamically typed languages, as they give us more flexibility, there is the very likely probability that people will write programs that go beyond what the specification allows.
My thinking was influenced by this question, when I read the answer by bobince:
A question about JavaScript's slice and splice methods
The basic thought is that splice, in Javascript, is specified to be used in only certain situations, but, it can be used in others, and there is nothing that the language can do to stop it, as the language is designed to be extremely flexible.
Unless someone reads through the specification, and decides to adhere to it, I am fairly certain that there are many such violations occuring.
Is this a problem, or a natural extension of writing such flexible languages? Or should we expect tools like JSLint to help be the specification police?
I liked one answer in this question, that the implementation of python is the specification. I am curious if that is actually closer to the truth for these types of languages, that basically, if the language allows you to do something then it is in the specification.
Is there a Python language specification?
UPDATE:
After reading a couple of comments, I thought I would check the splice method in the spec and this is what I found, at the bottom of pg 104, http://www.mozilla.org/js/language/E262-3.pdf, so it appears that I can use splice on the array of children without violating the spec. I just don't want people to get bogged down in my example, but hopefully to consider the question.
The splice function is intentionally generic; it does not require that its this value be an Array object.
Therefore it can be transferred to other kinds of objects for use as a method. Whether the splice function
can be applied successfully to a host object is implementation-dependent.
UPDATE 2:
I am not interested in this being about javascript, but language flexibility and specs. For example, I expect that the Java spec specifies you can't put code into an interface, but using AspectJ I do that frequently. This is probably a violation, but the writers didn't predict AOP and the tool was flexible enough to be bent for this use, just as the JVM is also flexible enough for Scala and Clojure.
Whether a language is statically or dynamically typed is really a tiny part of the issue here: a statically typed one may make it marginally easier for code to enforce its specs, but marginally is the key word here. Only "design by contract" -- a language letting you explicitly state preconditions, postconditions and invariants, and enforcing them -- can help ward you against users of your libraries empirically discovering what exactly the library will let them get away with, and taking advantage of those discoveries to go beyond your design intentions (possibly constraining your future freedom in changing the design or its implementation). And "design by contract" is not supported in mainstream languages -- Eiffel is the closest to that, and few would call it "mainstream" nowadays -- presumably because its costs (mostly, inevitably, at runtime) don't appear to be justified by its advantages. "Argument x must be a prime number", "method A must have been previously called before method B can be called", "method C cannot be called any more once method D has been called", and so on -- the typical kinds of constraints you'd like to state (and have enforced implicitly, without having to spend substantial programming time and energy checking for them yourself) just don't lend themselves well to be framed in the context of what little a statically typed language's compiler can enforce.
I think that this sort of flexibility is an advantage as long as your methods are designed around well defined interfaces rather than some artificial external "type" metadata. Most of the array functions only expect an object with a length property. The fact that they can all be applied generically to lots of different kinds of objects is a boon for code reuse.
The goal of any high level language design should be to reduce the amount of code that needs to be written in order to get stuff done- without harming readability too much. The more code that has to be written, the more bugs get introduced. Restrictive type systems can be, (if not well designed), a pervasive lie at worst, a premature optimisation at best. I don't think overly restrictive type systems aid in writing correct programs. The reason being that the type is merely an assertion, not necessarily based on evidence.
By contrast, the array methods examine their input values to determine whether they have what they need to perform their function. This is duck typing, and I believe that this is more scientific and "correct", and it results in more reusable code, which is what you want. You don't want a method rejecting your inputs because they don't have their papers in order. That's communism.
I do not think your question really has much to do with dynamic vs. static typing. Really, I can see two cases: on one hand, there are things like Duff's device that martin clayton mentioned; that usage is extremely surprising the first time you see it, but it is explicitly allowed by the semantics of the language. If there is a standard, that kind of idiom may appear in later editions of the standard as a specific example. There is nothing wrong with these; in fact, they can (unless overused) be a great productivity boost.
The other case is that of programming to the implementation. Such a case would be an actual abuse, coming from either ignorance of a standard, or lack of a standard, or having a single implementation, or multiple implementations that have varying semantics. The problem is that code written in this way is at best non-portable between implementations and at worst limits the future development of the language, for fear that adding an optimization or feature would break a major application.
It seems to me that the original question is a bit of a non-sequitor. If the specification explicitly allows a particular behavior (as MUST, MAY, SHALL or SHOULD) then anything compiler/interpreter that allows/implements the behavior is, by definition, compliant with the language. This would seem to be the situation proposed by the OP in the comments section - the JavaScript specification supposedly* says that the function in question MAY be used in different situations, and thus it is explicitly allowed.
If, on the other hand, a compiler/interpreter implements or allows behavior that is expressly forbidden by a specification, then the compiler/interpreter is, by definition, operating outside the specification.
There is yet a third scenario, and an associated, well defined, term for those situations where the specification does not define a behavior: undefined. If the specification does not actually specify a behavior given a particular situation, then the behavior is undefined, and may be handled either intentionally or unintentionally by the compiler/interpreter. It is then the responsibility of the developer to realize that the behavior is not part of the specification, and, should s/he choose to leverage the behavior, the developer's application is thereby dependent upon the particular implementation. The interpreter/compiler providing that implementation is under no obligation to maintain the officially undefined behavior beyond backwards compatibility and whatever commitments the producer may make. Furthermore, a later iteration of the language specification may define the previously undefined behavior, making the compiler/interpreter either (a) non-compliant with the new iteration, or (b) come out with a new patch/version to become compliant, thereby breaking older versions.
* "supposedly" because I have not seen the spec, myself. I go by the statements made, above.