To the Mr. Harper's observation we should add that the trinitary theory of computation needs to express itself in reality and it does so thru the quadrant of hardware: transistors, memory, electricity and machine code. Thus three, standing on the four, represents computation in action.
Could anyone suggest a happy path ("zero to hero") book on formal verification, which also does the ecosystem review for the formal verification languages, and then focuses on one, as well as provides the reasoning and mentions tradeoffs for such a choice?
https://softwarefoundations.cis.upenn.edu/ is a great zero-to-hero resource (it's what I used), but to my knowledge it doesn't have an ecosystem review in it. It uses Coq, but the first book has been ported to at least Isabelle, if not others.
Formal Methods An Appetizer is excellent and has a companion site with toy formal methods implemented in F#: http://www.formalmethods.dk. However, it does not cover proof assistants.
Alternatively you may enjoy these two. The latter is especially thorough, and begins with classical logic:
Since the whole premise here is that formally verifying the properties of programs is equivalent to constructive mathematics, I think you might be asking for too much, modulo your definition of hero.
I'm a fan of formal verification of functional programming. It saddens me to say that, in that line of work, hardly anyone speaks English [0], at least not past the front door. The amount of high-horsery and dog-whistling is enough to wear down any regular folk that walks down the path. Maybe it's the nature of the field itself, that those accomplished folks seem to forget they once walked this earth, up right, on two legs.
Agreed! The most famous mathematician in the field told me “I used the first tool I could understand how to write a proof in.” Most he could not!
I was so disgusted by the tutorials for Coq that I wrote my own. It has been reviewed positively on Hacker News before. If you want a look at some of the basics of formal proof and these tools, take a look:
but then, wouldn't the textbook for "MA/CS 301 Introduction to Formal Methods" be the book to read, and you either don't have the prereqs for it (can't understand it maybe because you don't know any functional languages) or it takes you as far as it does and then you decide if that's as far as you want to go?
> the textbook for "MA/CS 301 Introduction to Formal Methods"
my bad, I was using a "slangy" sort of description to say "shouldn't somebody read the textbook that would be used in a computer science 101 class (or a math 101 class) except not 101 but a hypothetical 3rd level class" so I called it 301. I was asking, "what textbook is used in an upper level undergraduate course on the topic?"
Many American universities use a numbering system of 101 & 102 for the fall and spring term classes somebody majoring in the subject would take, and 201 - 202 for the 2nd year. A CS100-level class would be a lighter survey for somebody who would want to study some CS but not major in it.
Programming in dependent types with univalence (Homotopy Type Theory) is an awesome way to see this realized.
The typing statement has to be proven by realizing the isomorphism demanded by substitution. You are more than anything directly proving what you claim in the type. Since proof is isomorphism here, the computation in terms of lowering the body of the definition to a concrete set of instructions is execution of your proof! (possibly machine code or just abstract in a virtual machine like STG). The constructive world is really nice. I hope the future builds here and dependent types with univalence is made easier and more efficient.
For dependent types, I would look at Idris [1]. Adding Univalence in a satisfying way is I think still somewhat of a research question (I could be wrong, and if anyone has any additional insight would be interested to hear), i.e. see this thread about Univalence in Coq [2]. There are some implementations in Cubical Type Theory, but I am not sure what the state of the art is there [3]
Assume that regular software has, on average, one bug every 50 lines. (All numbers made up on the spot, or your money back.) Let's suppose that Idris can reduce that to absolutely zero. And let's suppose that totally-working software is worth twice as much as the buggy-but-still-mostly-working slop we get today.
But Idris is harder to write. Not just a bit harder. I'd guess that it's maybe 10 times as hard to write as Javascript. So we'd get better software, but only 1/10 as much of it. Take your ten most favorite web applications or phone apps. You only would have one of them - but that one would never crash. Most people won't make that trade. Most companies that produce software won't make it, either, because they know their customers won't.
Well, you say, what about safety-critical software? What about, say, airplane flight control software? Surely in that environment, producing correct software matters more than producing it quickly, right?
Yes, but also you're in the world of real-time embedded systems. Speed matters, but also provably correct timing. Can you prove that your software meets the timing requirements in all cases, if you wrote it in Idris? I believe that is, at best, an unsolved problem. So what they do is they write in carefully chosen subsets of C++ or Rust, and with a careful eye on the timing (and with the help of tools).
I've been dabbling with Idris and agda and coq. I think I'm pretty much settling on agda, because I can appeal to Haskell for help. It's tough finding things that aren't just proofs, actually running a program isn't hard, there just doesn't seem to be many people who do it. I've got some toy projects in mind, and I'm going to lean hard on https://github.com/gallais/aGdaREP (grep in agda). I can't tell you if it's ten times harder - that seems high. It's different, sure. I'm having a tougher time than with, say, prolog. But most of the bumps and bruises are from lack of guidance around, uh, stuff.
So given that context, it doesn't sound to tough to add a cost to the type for each operation, function call, whatever, and have the type checker count up the cost of each call. So you'd have real proof that you're under some threshold. I wouldn't put the agda runtime on a flight control computer. But I think I could write a compiler, now, For like a microcontroller that would count up (or spend time budget, doesn't matter).
A more sophisticated computer would be way way harder, and be resource efficient. But if you modeled it as "everything's a cache miss" and don't mind a bunch of no-ops all the time, that would be a pretty straightforward adaptation of the microcontroller approach.
I would recommend trying Lean4 because I think it is better suited to programming. Lean has Rust-like toolchain manager; a build system (cf. `.agda-lib`); much more developed tactics (including `termination_by`/`decreasing_by`); more libraries (mathlib, and some experimental programming-oriented libraries for sockets, web, games, unicode...); common use of typeclasses in stdlib/mathlib; `unsafe` per declaration (cf. per module in Agda); sound opaque functions (which must have a nonempty return type) used for `partial` and ffi; "unchained" do-notation (early `return`, imperative loops with `break`/`continue`, `let mut`); easier (more powerful?) metaprogramming and syntax extensions. And in Agda you can't even use Haskell's type constructors with type classes (ex. monad polymorphic fns, and that makes it more difficult to make bindings to Hs libs, than to C libs in Lean).
There are features in Agda/Idris (and probably Coq, about which I sadly know almost nothing) that are absent from Lean and are useful when programming (coinduction, set omega, more powerful `mutual`, explicit multiplicity, cubical? etc), but I'd say the need for them is less common.
IntelliJ Arend probably has the most comprehensive support for HOTT among the proof systems: https://arend-lang.github.io/ . Not a lot in the way of tutorials though, just the official documentation.
Lamport’s Computation and State Machines[0] is an interesting take on relating mathematics to computer programming. Lamport appears to treat programs as state machines, making it possible to reason about those with pure mathematics (in a way independent of programming language syntactic constructs).
This is my favorite CS paper of all time. Reason being, it distills multiple different areas of CS down to one idea: state machines. Now I'm frequently able to map a complex idea down to a state machine, which makes all kinds of problems more manageable.
You can also express the "programs as state machines" view in terms of programming language constructs, namely monads (for Time, State and Non-Determinism). If you take a strictly mathematical view of the topic you'll call these 'modalities' or 'modal operators' instead but it's the same deal.
Accessible for who, do you mean? That is in no way 'accessible' without solid exposure to quite a lot of discrete maths (which I have and it's still hard going).
It's accessible to anybody with any experience programming and who is willing to pay attention. You are just taking a formula out of context, omitting the plain English explanation that comes before it, and pretending it's some kind of alien math. It's not scary at all, in fact it's very simple.
Let's do this in Python. First, write a function that curries:
def curry(f):
def g(x):
def h(y):
return f(x, y)
return h
return g
my_func = lambda x, y: 10 * x + y
curried = curry(my_func)
print(curried(5)(3))
# output: 53
Now, let's type it in such a way that mypy --strict won't complain:
from typing import TypeVar, Callable
A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')
def curry(f: Callable[[A, B], C]) -> Callable[[A], Callable[[B], C]]:
def g(x: A) -> Callable[[B], C]:
def h(y: B) -> C:
return f(x, y)
return h
return g
What's the type of curry? Let's hover our cursor over the function name, and lo and behold:
This would be a really good (and much more accessible than the linked pdf) explanation without the condescending tone. This is some pretty advanced math, and pretending otherwise isn't constructive. If someone says "I'm having trouble understanding this", it's not their fault, and it's generally not laziness - it just means that the explanation isn't suited to them.
It's not any kind of advanced math at all! The quoted fragment above is just a line of computer code. Anyone who has done any programming in any typed language should be easily able to understand it, though they may need to look up the syntax.
Nope, not me. But I've only been programming for 40 years, so maybe I'll get it when I've got more experience.
In other words, baloney. If you don't do that kind of programming, that stuff is very opaque. I might be able to get it if I were willing to stare at it for another 15 minutes, but I'm not willing.
Well, I interpret that as the assertion that it's more opaque than other types of programming. But I disagree and think that it is actually simpler in terms of the syntax and amount of prior understanding required. My blunt reply is that to assert that a particular example is inaccessible but then only to have dedicated 15 minutes to prove so is silly. I'm sure most people who seem to suggest that this stuff is opaque had no problem learning PCRE or complicated SQL joins and also didn't complain that it took more than 15 minutes to do so. Of course TT is a deep field and there are many complicated parts of it, but the syntax, rules and the example given are not opaque and very understandable to anyone who can tackle languages and abstraction.
Okay then. Please give some links to the following.
– Lambda calculus tutorial that gets you up to what you need to understand the referenced paper in 15 minutes
- A description of Hoare notation that I can also understand to adequate depth to understand the referenced paper, also in 15 minutes
- A description of classical logic etc. etc. 15 minutes
- A description of intuitionistic logic etc. etc. 15 minutes (presumably in how it differs from classical)
These would be very useful. If you can also explain them in a way that would allow me to use the information I've learnt in an hour to do something useful with programs like represent them, transform them, verify them against a specification, I would gladly spend a couple of weeks or a month doing that, genuinely.
If you can't do any of these things, please stop posting how easy it all is
You're right, I should read more carefully. Nonetheless, my point still stands, if you can give me some resources to learn these things, with considerably more than 15 minutes allowed, and bearing in mind I have no one to ask when I get stuck, and at my end aim is to actually use these things rather than treat them as interesting (which I already find them so) then I'd be grateful.
> You are just taking a formula out of context, omitting the plain English explanation that comes before it,
which, this?
"We saw earlier in the course that we can curry a function. That is, given a function of type (τ 1 ×τ 2 ) → τ 3 , we can give a function of type τ 1 → τ 2 → τ 3"
That helps no-one unless you have some serious hand-holding. I know the type of curry, I've written curry/bind/whateveer in C#, scala, JS and likely others. I have a background in this. It's not 'accessible' to mere mortals and even I'm not familiar with some of the notation here. Stop pretending it's just laziness on other people's part.
(oh yeah, did I forget to put constructive logic in my list above? Brouwer's intuitionism? FFS get real, I never even heard of that until a few years ago)
It is accessible if you know a few bits of syntax, such as → being right-associative so τ1 → τ2 → τ3 means τ1 → (τ2 → τ3). And others such as "f: X → Y declares f as a function from X's to Y's." which you can find in any math textbook. The point of using such a terse notation rather than faffing around with Callable[…] is to make it possible to work with larger examples without getting bogged down in the verbiage. It's why we use symbols to solve equations in K-12 math.
If someone can learn C type syntax this is MUCH simpler. That doesn't mean you don't have to spend a little bit of time learning how it works, but it is not some kind of number-theory level maths construction only accessible to savants.
By that definition "Baby Rudin"[1] is an accessible introduction to analysis as long as you're willing to look up what you don't understand (ie everything probably the first time) in some other source.
If you can learn how to program, you can learn how to understand this. You first must learn the notation, just as when you learned "def", or "func", or "[x for x in ...]", or "int main() { ... }", or whatever your favorite language's notation is.
"Accessible" doesn't necessarily mean "with just knowledge of English, you can jump to this example and immediately understand what it means". Some work is always required; accessible just means this work is not too hard.
For example, Python's notation has been occasionally described as "accessible", yet I wouldn't expect my mum to immediately understand a non-trivial Python snippet (so no "print('hello')") without any previous explanations.
Maybe if you view "programming" as one homogenous block of understanding, but there is a difference between doing basic low-level programming and effectively writing abstract code in Haskell. I can only speak for myself but layers of abstraction make things exponentially harder for me to follow. Compact mathematical notation on top of that feels a bit exhausting.
It can probably be called accessible for people already interested or talented in maths, but there's a reason most people don't just casually learn astrophysics for fun even though some introductory material might be considered accessible - not everyone can do it without unreasonable amounts of effort.
> Maybe if you view "programming" as one homogenous block of understanding
Oh, I'm not saying it's the same as programming in your favorite language.
I'm saying if you have the mental machinery to understand a new language, you also have it to learn lambda calculus. Trust me, it's not that hard. It just looks extraneous out of context because you are not familiar with it.
> It can probably be called accessible for people already interested or talented in maths
I'm not particularly good at maths and I found introductory-level lambda calculus not very difficult. The notation is different from, say, a C program. You just learn this notation, which your professor will explain. Nothing too terrible.
> layers of abstraction
I'm confused. Which layers of abstraction? I mean, it's an abstraction just like C or Python or "1 + 1" are abstractions. There are rules and you learn how to apply them.
It's about the entire set of formalisms given in the pdf, not just lambda calculus. The argument here about LC was because I had just given a snippet of it, but it's not the whole paper.
Now, the next bit – so I've spent my 15 minutes reading up on each of these formalisms (lambda calculus, intuitionistic logic, Hoare notation, whatever else). So now I can read the paper and I do, and now I have absolutely no way of making use of anything I've learnt. I spent personally a lot of time on the computer science side of things, and I have got frankly just about nothing out of it. So frankly again, there's no point learning it (from a utilitarian perspective).
When I was a kid I picked up a book on Fortran 4 and tried to learn it from the book, without a computer. That's pretty much the situation here – a passive notation that, had I managed to internalise it, would have been completely useless because I had no computer to run it on. And I'm tired of this discussion of people saying it's easy and it's useful, because it's not easy, and it's not useful without a whole lot more studying an application to bridge the abstraction of the formalism to the actual programming environment where I can use it.
Ok, but now you are making a completely different claim!
You started out saying, "oh, these lecture requires some advanced math background to understand", and quoted the curry function. And then a lot of people explained that no, there is no advanced math, this is just a function written in a programming language. And now it seems you agree, if you spend only 15 minutes you can understand the lecture notes. That's all I, and others, wanted to say.
Separately, it's the case that understanding this will not be very useful in a day-to-day programming job. That's just because it's not all that useful! (See other discussion on this this same HN page.) I think it is very cool and philosophically interesting in itself, and it offers an interesting point of view if you are designing new programming language type systems, but it doesn't have very much "utilitarian" use.
> And now it seems you agree, if you spend only 15 minutes you can understand the lecture notes
that was sarcasm.
> I think it is very cool and philosophically interesting in itself, and it offers an interesting point of view if you are designing new programming language type systems, but it doesn't have very much "utilitarian" use.
It has phenomenal use is my point (eg. formal verification, an interest of mine), but only at a vastly higher level than "I've been to the lectures". What I keep saying is, most people aren't interested in the abstract, and neither am I. Give me a practical application and suddenly I'll 'get it'. Without that application it's valueless because I have no reason to engage with it, and actually cannot. Put another way: its application and its understanding are interlinked. You can separate them; I and others can't.
I know this stuff is useful and I literally spent years trying to understand it. For someone like me it just isn't easy, and you keep persistently not understanding that.
I'm going to be cynical and make the following observation:
Given that there's this correspondence between Math and Programming Languages, what it says is just that if you understand types in programming language, there's no point in understanding the notations in math.
And this is especially true if you take a cynical attitude towards theorem provers in the sense that real proofs are in the minds of the thinker, not in the formalism.
The fact that the Curry-Howard correspondence is was proposed in the 1930s suggests that it was a relatively "primitive" analogy that might be restated in some obvious way in today's language of computation. Maybe... "you can write programs to be automated theorem provers for formal systems"?
I wonder what CS people would have to say about the efforts "to prove a program correct". If a program is equivalent to a proof, "proving a program correct" seems to be "proving a proof correct".
I don't really know what I'm talking about though.
You've got the right idea. A program is a proof for the proposition made by its type. Only for the right languages though, and not in the naive sense of `assert(1+1 == 2)` proves the assertion.
OK, not what I expected. Surely a spec is a spec, the program fulfils the spec hopefully, and the proof is that the program implements the spec. How do you see it? thanks
Curry Howard shows that for a logical proposition (A) with corresponding constructive proof (B), there will be a type (A') with program (B'). It's not about proving desired properties of programs. However, you can use the CH principles to do this too, as long as you can encode the proposition about a program in its type. This will not often look like the dataflow type of the naive program though. Look at software foundations for real detail, I'm not expert at this stuff, just aware of the basics.
There's a lot of talk in these comments that assumes that certain people are just ‘built different’ and can grok complicated new concepts that are unrelated to anything they know by virtue either of being smart or of being nerds with no life.
I think there are several different types of ‘background’ required to read something, none of which are innate or unattainable without a particular kind of education (e.g. a university degree). This confuses discussions about whether something is ‘accessible’ because the person complaining that it is inaccessible might be complaining of a gap between their current state and the state assumed by the book, in any of those three categories.
1) Factual background.
What facts do you need to know, what theories do you need to understand, in order to be able to follow what the author is saying? If the author is trying to teach you classical mechanics but you don't know about addition, you're not going to get very far until you go and learn to add and come back.
A source can address this (reducing the amount of background required) by providing more background in the material itself. But it obviously doesn't scale that well if every explanation of classical mechanics comes with an explanation of addition (and counting, etc.), and the background you do get, if it isn't the primary goal of the material, is often rushed and not as well-written as a dedicated source. So instead (or as well as providing a quick refresher for people who already have the background but might have forgotten it) authors will often refer to other materials that provide better explanations.
Inaccessibility arguments from this perspective look like ‘I don't know what this word means’ or ‘I don't know how to read this notation’.
2) Motivational background.
What applications do you need to know about in order to find out worthwhile to learn a topic? If you don't know about bicycles, levers, etc., learning about classical mechanics can seem like an abstract waste of time.
As a function of the complexity of a topic, it necessarily takes a certain amount of time before the author's thoughts are complete enough to relate to something that the reader will find compelling, though a good author will try to minimize it. People vary here in their trust of an author, and there are certain external factors that can influence this as well: e.g. if the material is very well-regarded by other people the reader might be happier to spend time figuring out what it's trying to say, or if the reader has a background in a similar topic they might be able to imagine some motivations that will keep them going.
That's not to say that the reader should always have this: indeed, most materials are actually useless to most people. One of the pre-tasks of reading something is deciding whether it's something that is (potentially) even useful to read, and for most things your answer should be ‘no’.
Inaccessibility arguments from this perspective look like ‘I don't care about this’ or ‘this is abstract nonsense’. Note that text is never actually completely abstract: authors, like readers, are always motivated by practical concerns. But there might be a long chain of problems and solutions from a problem that the reader is aware of to the solution that the author is explaining — in fact, in pure maths, the applications of a theory aren't always even known — but there's good reason to believe that they exist.
3) Attention span.
Difficult concepts just take a long time to learn. This can be addressed somewhat in the text by providing longer explanations in which the concept is spelt out more elaborately, but then you risk alienating readers who've lost track of your point by the time you've got to the end of it — there's a balance to be struck, and the correct point on the continuum depends on your intended readership. Also, on the reader's end, this is to some extent a ‘muscle’ that needs to be trained and regularly exercised in order to not lose it.
All of these things lead to valid arguments of inaccessibility, but the failure isn't inherent in the writing: instead it indicates a mismatch between the writing and the reader. If the writing means to target a certain type of reader, that can make it badly written; but no writing can target every possible reader. If some writing seems inaccessible for you, if you're interested in it you can work on those three things to make it more accessible (which will involve some combination of staring at the target writing until it makes sense, and reading around related writings to get knowledge and practice reading about that kind of thing). Alternatively, you might be able to find other writings on the same topic with prerequisites that are a better match to you. For example, I've been trying to write a series of posts explaining CS concepts that I think are cool or useful, assuming the knowledge and motivation of an enterprise programmer, and drawing the chain of motivation/explanation as far back as necessary to get to concepts they're likely to know already. My writing on the lambda calculus is here:
Please excuse the sloppy writing, this is an attempt to get back into technical/explanatory writing after many years — but criticisms (with that particular intended audience in mind) are very welcome!
A quick skim says this is a really thoughtful bit of writing; I'll try to do it justice tomorrow but right now I can't. So thank you, and I'll try to respond tomorrow in a bit more depth. Thanks.
Certain proofs about a program, implemented using types, are useful in high level programming, as well as low level programming. See Rust in the linux kernel [1], and Rust on Android[2].
One way to think about it, is that proofs about a program are a continuum, rather than one or the other. Sure, going all the way, disallowing mutability and requiring types for everything, might make the system more provable, but slower as well.
There are some different ways to look at a program:
Corporate developer: "Programs are to be run, and occasionally read or proved correctly"
Donald Knuth: "Programs are to be read, and occasionally run"
Type Theorist: "Programs are to be compiled, and occasionally run or read"
Quiz: what is the opposite of patronising? Because that's what you're being. Not talking down to people but assuming more than is typical.
You're clearly highly intelligent, evidently more so than I am. Additionally my mind doesn't work like yours, I'm not an abstract thinker. Plonk stuff like this in front of me and I can decipher it eventually, if I can get some clear statement of the notation and its semantics anyway, and there is stuff in there I'm not familiar with.
Most people aren't abstract thinkers. They are motivated by practical concerns. I spent an enormous amount of time reading up on stuff like this and I really can't see the value to it to me as a programmer. It's deeply frustrating because I know it has value, but I just can't use it. It's interesting, I know it's useful, but I can't use it.
Please allow that other people think differently and are motivated differently, and don't assume that what's easy for you is for them. I wish I had your cranium.
I respect that different people will find different things hard or easy, so that a generalization is never truly possible (except, uh, this sentence?). On the other hand, speaking with absolutely no generalizations whatsoever is simply not possible, because so many caveats and exceptions would make communication impossible.
I do stand by my initial assertion that, in general, if you have a mind capable of learning a new programming language, you can learn lambda calculus, and it won't be particularly hard.
> Most people aren't abstract thinkers. They are motivated by practical concerns.
Abstract thinking is motivated by practical concerns, namely being able to think comfortably about larger problems without being limited by one's capability for complex reasoning. Most people don't do it because they aren't used to, or they have never been taught how.
> Don't talk rubbish about 'look it up'. You need a degree to get this. Not a metaphorical degree, the real thing.
You really don't need a degree. The Curry-Howard isomorphism is taught in undergraduate CS courses, to students who are simultaneously being introduced to lambda calculus.
While I wouldn't say it's trivial, it's not rocket science either. Most students get it and pass the exams.
I would interpret "accessible" here as meaning relatively accessible compared to what it could be. The underlying concept is inherently difficult, and so it will always be somewhat inaccessible, but within the space of all possible explanations, I would agree that it's on the more accessible end rather than the less.
Nifty. That was a nice 4-page exposé of the correspondence. There are no general proofs, but the correspondence between notations is very suggestive and invokes interesting intuitions, I think. The example derivations with the uncurry example are quite fun to work through. The paragraph about continuations corresponding to double negation is also super neat!
Now I just wish there were something similar for the missing category theory third of the trinitary mentioned in another comment.
Not just formally but also stylistically. Composable lemma and composable functions are two leaves of the same book. The construction of a proof is to convince the reader of the correctness of the logic and this is exactly the same goal of a mathematical author as it is of a computer programmer.
Business really interferes with the goal of the mathematical programmer — proofs of concept, hacks to get MVPs over the line, throwaway demos to investors etc. — but after a certain point the value of the codebase becomes entrenched into the value of the business and that’s when you need to bring in the mathematical coders to constantly refine and prune your codebase into something that will survive and bear out the earnings per share values.
I don't get it. Or rather, I don't get the significance of the Curry-Howard Isomorphism. Questions:
1) Is there an example of a proof and its corresponding program that makes you say "whoa, trippy, I didn't expect those to be related"? Like, yeah, an Int -> Boolean function "proves" you can construct a Boolean from an integer, but ... so what? What would a non-trivial proof (say, on the order of the Pons Asinorum) look like as a program?
2) Are the "programs" referred to here to merely pure functions (i.e. side effect-free, global state-free ones)? It sounds like it is based on lines like this:
>When a computer program runs, each line is “evaluated” to yield a single output.
That ... seems to cram "computer programs" into some kind of Procrustean bed unless we're taking about pure functions. But then it also talks about how CHI has applications in verifiable programs, which, I'm told, can reason about side effects.
Feel free to call me an idiot, as long as you can also inspire an aha-moment.
> I don't get the significance of the Curry-Howard Isomorphism.
* Every type system is a system of logic (and vice-versa.)
* So, if you have a System F, you can build a Haskell.
* If you have affine logic, you can make Rust.
Have a look at this parser combinator library[1]. In particular look at how many functions are marked "Totality: total". These are functions which accept all (well-typed) inputs and terminate in finite time.
> What would a non-trivial proof (say, on the order of the Pons Asinorum) look like as a program?
It would just be a program - that you write every day. If Pons Asinorum is expressed in twenty lines, then pick a twenty-line program. If you want something bigger, check out CakeML or seL4.
> But then it also talks about how CHI has applications in verifiable programs, which, I'm told, can reason about side effects.
Not every effect is a side-effect. A suitable definition of "side-effect" might be "an effect which is able to punch a hole through whatever type system decided it was valid." If your programming language represents effects as types, then it can reason-about/type-check them just as well as pure functions.
>Every type system is a system of logic (and vice-versa.) * So, if you have a System F, you can build a Haskell. * If you have affine logic, you can make Rust.
That seems to be different from CHI, which asserts a correspondence between proofs and programs, not between systems of logic and languages; if the latter is more general it probably should have, and would have, been phrased that way.
>Have a look at this parser combinator library[1]. In particular look at how many functions are marked "Totality: total". These are functions which accept all (well-typed) inputs and terminate in finite time.
Sorry, what's the connection to CHI here?
>It would just be a program - that you write every day. If Pons Asinorum is expressed in twenty lines, then pick a twenty-line program. If you want something bigger, check out CakeML or seL4.
I meant "non-trivial" to apply to the mapping as well. As best I can tell, CHI just says there's some useless program that maps to every proof, and a useless proof that maps to every program. So what? What is the significance of that? So I can write a meaningless program that corresponds to Pons Asinorum?
You're not really taking the challenge seriously here -- you're just asserting the same trivialities about which I was asking, "is that all there is?"
> That seems to be different from CHI, which asserts a correspondence between proofs and programs, not between systems of logic and languages
But proofs are written in systems of logic and programs are written in (programming) languages, so if there's a correspondence between the first there really ought to be a correspondence between the second!
Int -> Boolean is just the type of any arbitrary decidable property on Int's, so it's not very useful on its own. But if you can endow your Int -> Boolean function with a proof that it computes some property you actually care about, that's more likely to be helpful. The proof itself need not even be constructive, since the requirement for constructibility was taken care of by providing the code for that function. Constructibility becomes more of a big deal if you want to be able to compose constructive proofs, or write proofs that rely on instances of some other proof as input, since then the strategy of providing some construction as a bare algorithm and separately proving it correct may run into problems.
And if your return type is something other than Boolean, that represents some more general construction rather than mere decidability. But everything else is just about the same. Of course this all assumes a total language with no general recursion, since otherwise you can "prove" anything by just looping forever and not delivering a result.
> But if you can endow your Int -> Boolean function with a proof that it computes some property you actually care about, that's more likely to be helpful.
Agreed, but that's beyond the scope of what's asserted with CHI, right? CHI is asserting that my implementation of some type-correct[1] (int-to-)Boolean function is, itself -- irrespective of what I can prove about it -- equivalent to some other proof. What's that proof, and what's interesting about the proof or the mapping?
[1] i.e. really does take ints and always returns bools
CHI is just the observation that some logical steps in proofs behave as similar steps in programs. For instance, "Apply Lemma f to variable x with preconditions given by H1, H2; call the resulting statement L" is equivalent to a function call L = f(x, H1, H2) where H1, H2 are capabilities or abstract "tokens". Proof by cases can be expressed as a switch { } or match { } construct, and proof by induction behaves as recursion. This often simplifies the writing of "correct by construction" programs since a single construct can take care of both the computational and the logical side.
>CHI is just the observation that some logical steps in proofs behave as similar steps in programs.
It would have to be all, not just some, or it's not an isomorphism (or there are caveats about function purity I mentioned before).
>"Apply Lemma f to variable x with preconditions given by H1, H2; call the resulting statement L" is equivalent to a function call L = f(x, H1, H2) where H1, H2 are capabilities or abstract "tokens".
Okay, that helps. So where are the mind-blowing examples?
It's not quite "all" because non-constructive proofs exist. In an ordinary program you can't just switch between returning a value as output and requesting it as an input, but in proofs you can kinda do this - and that's a logical step called "proof by contradiction".
There are some connections in mathematics that are quite "magical", where you get something like a correspondence between a family of curved surfaces and a family of integer-valued equations, and it's not at all obvious why that might be until you study it deeply.
And sometimes articles about the Curry-Howard Isomorphism suggest that it's an example of that sort (I think this article does, to some extent).
But (as I understand it), that isn't the case: here we have a simple directly-constructed correspondence. It's valuable, but not the sort of thing that mathematicians think of as a wonderful result.
I also still don't get it, years later. I wrote a short post on this (a bit too inflammatory, tbh, sorry for the URL): https://blag.cedeela.fr/curry-howard-scam/ . A lot of logic and theorem proving can be done without ever thinking about CH. In classical logic I think it's not even that convenient anyway, and classical logic is a pragmatic choice.
1) The trippy one I know of is that the Church encoding of numbers is the induction principle for naturals.
2) Generally the programs that are proofs you would want to be pure, but it wouldn't be strictly necessary. The impurity would have to be handled by the type system though, so explicit effects not side-effects.
Also, note that even though you want the programs-that-are-proofs to be pure, that doesn't mean you can't prove things about programs that are impure - that is, you have a pure-program that has a type, and that type is a proposition about a different impure program.
That is, I can create a pure program that is a proof about my impure program.
EDIT: I think something that is a sticking point for a lot of people is looking for a program that is useful by itself that is also the proof of something useful. This is possible, but a bit rare-er - oftentimes you have useful programs with trivial types, or useful programs-that-are-proofs that you don't actually care to run. These are still super useful though! It is possible to have ones that are both - decidability of things is what comes to mind. You could write a program that determines if two naturals are equal - that's a useful program, albeit one of the simplest - and that program also serves as a proof that two naturals are either equal or not - a kind of particular instantiation of the law of excluded middle. One style of programming with dependent types always returns a proof along with the returned value, which might be kind-of what you're looking for. (Think of a regular expression engine that along with the yes/no does this string match this regular expression, returns a proof that the string does or doesn't match, demonstrating its own correctness)
When you compute a pure function, you’re still physically manipulating registers, cache, etc.
You can use the proof-program perspective to formalize reasoning about a program:
- you have some domain model; this expresses the “business logic” of your software
- you have an abstract model, in a category for your programming language; this expresses an equivalent structure as the “business logic”
- you have a concrete model, in a category for your hardware; this expresses an equivalent structure as it gets executed
Reasoning about the translations between these steps, their properties, etc is why you want to connect software to proofs.
For example, if you want to find an optimal implementation: you want the shortest path of atomic arrows in your hardware category, which corresponds to the desired computation in your language category.
Category theory is the language in which we connect our theory of hardware to our theory of the business domain!
2) Being restricted to pure functions is not actually much of a restriction at all. Any impure input can be trivially replaced with parameterization over values representing that input and any impure output can be trivially replaced by including state updates or the updated state in the return value. Then your entire codebase can be entirely pure and you can let the language runtime or the compiler deal with that pure data at the very top level of the program, performing the IO as needed. This may be slightly more inconvenient than allowing impurity, but not enough that it would be by any means infeasible, and languages like Haskell have facilities which narrow the gap even further, so it's not much of a restriction at all.
Maybe not trippy, but a simple set of examples are existence theorems, i.e. "Given these objects, X exists." An example are fixed point theorems.
Theorem: For any function g from a convex, compact set to itself, there exists a fixed point of g.
program f: g --> x where g is a function from a convex, compact set C to itself, and x satisfies g(x) = x.
If you can write the program and it is correct for all such g, that is a proof that such a g always has a fixed point (in particular, you output one). Note the "type" of g is "function from convex, compact set to itself" and the "type" of x is "fixed point of g".
You won't see many examples of "traditional" languages taking advantage of Curry-Howard. Though you can, e.g. an `Admin` class whose constructor checks that the credentials are for an admin, and functions like `deleteDatabase` which take an `Admin` instance and don't check for admin credentials because they're already "proven".
You can't even allow functions which infinitely loop: in theorem-proving languages, every function must be proved terminating. Otherwise you allow:
anything : a
anything = recurse 0
where recurse i = recurse (i + 1)
But in theorem-proving languages, the idea that "this type represents a logical statement, an instance only exists if it's true" is used very often. A classic example is fixed-size vectors
data Nat where
0 : Nat
S : Nat -> Nat -- n + 1, "S"uccessor
data Vec (n : Nat) a where
Nil : forall a, Vec 0 a
Cons : forall n a, a -> Vec n a -> Vec (S n) a
You will see declarations like:
-- Instances of `IsTrue b` only exist if `b = True`
data IsTrue (b : Boolean) where
Trivial : IsTrue True
-- Instances of `Every pred vec` only exist if every element in `vec` satisfies the predicate (so that `pred elem = True`)
data Every (pred : a -> Boolean) (vec: Vec n a) where
Every_nil : forall pred, Every pred Nil -- Every item of an empty vector satisfies an arbitrary predicate
-- Prepending an element which satisfies some predicate to a vector where every element satisfies the same predicate, produces a vector where every element satisfies the same predicate
Every_cons : forall pred x xs, IsTrue (pred x) -> Every pred xs Every pred (Cons x xs)
foosAreFoo : Every (\n -> n == "foo") (Cons "foo" (Cons "foo" (Cons "foo" Nil)))
foosAreFoo = Every_cons Trivial (Every_cons Trivial (Every_cons Trivial Every_nil))
On the other hand, the requirement that all functions must be "logical statements" is essential. Otherwise the programs will crash and the generated proofs will be illogical (and, notice that "crashing program" = "illogical proof"). For example, if one can define the following (impossible to prove) function, one can create programs which crash trying to extract the first element of an empty vector, and proofs which incorrectly assume that all vectors have a first element.
head : Vec n a -> a
head Nil = ???
head (Cons x _) = x
One can define this function though, taking advantage of the fact that there's no such instance `Nil : Vec (S n) a` so only the `Cons` case needs to be matched (which is why this typechecks even though we didn't handle the `Nil` case, while the above example doesn't. Sorry if it's confusing and/or sounds like a cop-out, that's just how it works and real languages accept this kind of pattern matching):
head : Vec (S n) a -> a
head (Cons x _) = x
And, to answer the second point, theorem-proving languages can represent and prove properties of programs with side-effects and even straight-line programs. The former is commonly done using Monads or Algebraic Effects, and the latter using Hoare Logic or another kind of logic
data Var a = String
-- Example IO monad which represents side-effects (stdin, stdout, and variables) through constructors
data IO a where
Pure : a -> IO a
ReadLine : IO String
PrintLine : String -> IO ()
ReadVar : Var a -> IO (Maybe a)
WriteVar : Var a -> Maybe a -> IO a
(>>=) : IO a -> (a -> b) -> IO b
-- "Pure" program which reads first and last name and prints full name.
-- The "interpreter" lazily computes the value of `main` and simultaneously evaluates `IO` actions to get their inner values:
-- When it encounters (x >>= y) it computes `x`, evaluates `x` to get the inner value at runtime,
-- then computes and evaluates `y`
main : IO ()
main =
PrintLine "What is your first name?" >>= \() ->
ReadLine >>= \firstName ->
PrintLine "What is your last name?" >>= \() ->
ReadLine >>= \lastName ->
Pure (firstName ++ " " ++ lastName) >>= \fullName ->
PrintLine ("Hello " ++ fullName ++ "!")
foo : Var Int
foo = Var "Foo"
-- Proving properties of programs is done with Hoare Logic.
-- This is a lot more complicated and full of boilerplate...
data Predicate where
True : Predicate
(!==) : forall a, Var a -> a -> Predicate
(/\) : Predicate -> Predicate -> Predicate
data HoareTriple (pre : Predicate) (stmt : IO ()) (post : Predicate) where
Obvious : forall pre stmt, HoareTriple pre stmt True
Merge : forall pre stmt post1 post2, HoareTriple pre stmt post1 -> HoareTriple pre stmt post2 -> HoareTriple pre stmt (post1 /\ post2)
WeakenL : forall pre1 pre2 stmt post, HoareTriple pre1 stmt post -> HoareTriple (pre1 /\ pre2) stmt post
WeakenR : forall pre1 pre2 stmt post, HoareTriple pre2 stmt post -> HoareTriple (pre1 /\ pre2) stmt post
Specific : forall varN n, HoareTriple (varN !== n) (ReadVar varN >>= \nValue - WriteVar (varN + 1)) (varN !== (n + 1))
example : HoareTriple
(foo !== 4 /\ foo !== 5) -- Precondition
(ReadVar foo >>= \fooValue -> WriteVar (fooValue + 1)) -- Statement
(foo !== 5 /\ foo !== 6) -- Postcondition
example = ... -- Some combination of Merge, WeakenL, WeakenR, and Specific, but I've written enough
On a tangent. I think its worth it to push typed mathematics waaaaay down into highschool. While students are learning multiplication, the teaching tools/question/answers need to teach how that changes the result's units (types). Highschool physics especially needs to have 'proper units at every stage of calculation' as part of the test.
ps. and our calculators (& excel) needs better support for it
> Highschool physics especially needs to have 'proper units at every stage of calculation' as part of the test.
Did your highschool physics not have this? I always thought it was a universal part of all physics education. Doing things like canceling out units etc. has always been a big part of high school physics, and checking that your final answer has the units it should. (If it didn't, you made a mistake somewhere.)
The idea of building units into Excel is definitely an intriguing one, though. I'm honestly kind of surprised it's the first time I've ever heard it suggested. It does seem like a pretty useful idea.
Unit analysis is absolutely a graded element of the AP test. The American educational system has enough to point and laugh at without fabricating new stuff.
> Highschool physics especially needs to have 'proper units at every stage of calculation' as part of the test.
I can't imagine any HS science class not already doing this. It would be impossible to answer many, if not all, of the questions correctly if you mix units (either unit-kind like mixing time and distance units, or unit-scale like mixing seconds and hours).
Types and units are very different concepts. In fact it's quite hard to formalize the way units are used in everyday physics.
For example, what is the type of the / operation such that 4m/2s = 2m/s, but also 4kg/2kg = 2?
It's not impossible of course, but it is highly tedious and complex to actually define these types in a formal way.
Not to mention, linear algebra (matrix operations) gets REALLY ugly to define formal types for really fast if you allow different measurement units for every matrix element, like you often do in physics.
And, just like the others, I never met a physics class that didn't enforce unit maths at every step of computation, starting in sixth grade.
But this is done with a simple informal system, not type theory of all things. The informal system of measurement units being essentially to treat the units as special values that act as factors and obey all the usual rules of multiplication and division, and don't allow addition or subtraction.
Most maths teaching emphasises this but they don't talk about it in quite the same way. Units are dealt with as units whenever you are doing real world problems around velocity, speed, physical quantities etc and you are taught not to mix units, and how units "cancel" etc each other when you deal with exponent laws.
Types as such are dealt with as sets. So for example I'm working my way through Serge Lang's "Basic Mathematics" at the moment[1], and it starts with the natural numbers, then the positive integers, then the integers, then the rational numbers, then the reals etc. This is very normal for high-school level maths education.
I believe that mathematically the formal theory of types comes from a different branch from sets which arose when Russel attempted to address the problems caused by his paradox. "Type" theory was part of Russel's solution whereas Zermelo Fraenkel set theory is where everyone else felt that sets just needed a little patch to carry on working pretty much as before.
[1] Which I'd really recommend for anyone who wants a maths refresher that starts from very basic concepts but really challenges you to think like a mathematician, prove things etc. So in the first chapter when you only know the distributive and associative properties he has you using them to prove stuff.
A good way to do this is to replace Geometry class. It is only there because Euclid’s Elements was read by Charlemagne’s kids. Geometry teaches proof, but a very outdated style of proof. It should be replaced with formal proofs, written in Lean or Coq. Since “a proof is a program”, it would also introduce programming into the curriculum.
I don't know that much about it, but the impression I got from studying TLA+ was that machine-executable code was distinctly not mathematics, and that programs are never provable. Am I wrong? Or is this just the kind of sensational snake-oil that HN readers are susceptible to buying.
If I understand what you are asking about correctly, then I do think you are mistaken.
As a sibling comment observed, you would be proving something about a program, but proving things about programs is both possible and done.
This ranges from things like CakeML (https://cakeml.org/) and CompCert (compilers with verified correctness proofs of their optimizations) to something simple like absence of runtime type errors in statically strongly soundly-typed languages.
Of note is that you are proving properties of your program, not proving them perfect in every way. The properties of your program that you prove can vary wildly in both difficulty and usefulness. A sufficiently advanced formally verified compiler like CakeML can transfer a high-level proof about your source code to a corresponding proof about the behavior of the generated machine-executable code.
Typical proofs don’t cover side channels, just ISA semantics. With the big asterisk that most programs are running on top of an unverified kernel on an unverified machine-level blob on an unverified ISA implemented by an unverified chip, you can absolutely prove the behavior of programs. Lamport has an unusual attitude towards verification and comes towards it from a slightly different angle, and model checking is not a good approach to machine-level proofs.
"One way to resolve the paradox, therefore, is to put these types into a hierarchy, so they can only contain elements of a “lower level” than themselves. Then a type can’t contain itself, which avoids the self-referentiality that creates the paradox."
This is similar to constraints of making something semi-decidable or recursively enumerable.
Provable, recursively enumerable, semidecidable, and turing recognizable are all the same thing described in different ways.
Some things are easier to find in type theory, other in set theory and for some Turing machines work better.
The Church–Turing thesis isn't provable but is the safe assumption.
IMHO termination analysis, which will never be complete, has more interesting and has some implications for ATP that aren't as easily captured in type theory.
As an example related to term rewriting which will be important to ML.
I think you misunderstand. On your point about programs not being "provable": it is certainly possible to prove some properties about some programs. It is not necessarily easy, and it very much depends on the program and the property in question, but it is possible.
However, this is not what the article is about. Instead, it talks about an interesting observation that there is a direct correspondence between a certain kind of program and a mathematical proof, and also between the type of the program, and the theorem validated by the proof. In other words, you can think of mathematical proofs as computational objects. The intuition for the correspondence is not hard: for example, support I want to prove that "if A is true, then B must also be true". You may think of a proof for such a property as a program, which takes as input a proof that `A` holds, and as its output produces a proof that `B` holds.
It doesn’t matter. What matters is if you can prove things you care about true for code you care about.
And yes we can prove termination and zero bugs for a lot of practical useful code. Examples: seL4 is a proven correct micro kernel and CompCert is a proven correct C compiler.
The trick is to use programming languages that are total i.e. not general infinite tape turing machines.
What does it mean to say "a program is provable"? You have to prove something about it.
In principle, nothing prevents you from taking e.g. an ARM binary and formally proving that it will never crash in some environment, e.g., bare metal chip with known amounts of memory etc. This is very hard for useful programs and not worth it in pretty much any application there ever was, but it's possible and getting ever easier over time.
Most code is so complex that even with the aid of a computer we couldn't run a proof on it if we tried. Most (all!) code also has bugs so if we tried to prove it we would instead prove it doesn't meet requirements. However the theory itself allows that all code could be mathematically proven if you can work around those two issues.
It also isn't clear that the machines itself actually do what they say, though hardware is a lot more likely to be mathematically proven.
> hardware is a lot more likely to be mathematically proven
Maybe that's a nitpick, but I would say it's fundamentally impossible to mathematically prove anything at all about a physical system. You have to assume some model to do the proof and have no way of ever "proving" (what would that even mean?) that model.
I guess you're referring to the fact that verification and formal methods are used in hardware more often than software. This is due to commercial reasons: you can't just reprogram a million chips once they leave your fab.
We run proofs on code all the time, it's called type-checking.
What you mean is that it's practically infeasible to verify every program behaviour we're interested in. That's probably true for large enough programs, but it doesn't mean we can prove nothing about them.
But we never need to prove the general case, only specific cases. The general case includes an infinite number of inputs, but in reality there is only a finite number of states a physical computer can have (the number of states far exceeds the number of atoms in the universe, but it is still finite)
General case in what meaning? You do have to prove (the interesting properties of) a given algorithm to each possible input, e.g. no one would want to use a sort algorithm that loops forever on a specific list of integers.
And the number of possible states increase so fast that you might as well think of it as infinite.
So long as the list is finite and no being modified outside of sort that can be done for some sort algorithms. Bogo sort wouldn't. If < works as expected (a<b means b<a) most algorithms will sort algorithms terminate, and a few don't even need that (though < working is needed to prove the result is ordered ).
Gödell showed there are algorithms that we cannot prove, but he did that with self referencing algorithms which we rarely use.
So long as the number of states is finite theory says we can prove by exhaustion. Either a state terminates, or it becomes a different state. Then you do a tree seach until you have reached a termination or a state already in the tree. You need more memory than the number of states to do this, but theroy allows for infine memory in the proof.
Computer scientists routinely prove theories with an infinite number of states. It is actually usually the easiest way to prove things because you don’t have to handle edge cases.
A typical theory says:
For all x:A, P x
In other words, given any x (from an infinite set A) P x is true. Where P is a proposition.
You typically prove it using straightforward induction/recursion.
Remember the halting problem is proving within a system, we can often step outside the system to prove it. Which sometimes allowes for more properties to be proved'
The halting problem, as I generally think of it, is to receive a program/specification-of-a-machine and output whether it halts. In this formulation, there is no system in which things need proving. Instead, the analogous thing is that, for any program that returns one of “halts”, “doesn’t halt”, or “unknown” and only returns the first two in cases where those are correct, there are other machines which also do that, but where they answer “unknown” for a smaller set of inputs.
(E.g. you can just augment the program by hard-coding in the correct value for some input that it previously answered “unknown” for.)
Of course, what you are saying about “stronger systems can prove the answers to the questions of the whether the machines from larger sets, halt” is also true, I just, wouldn’t describe this as “the halting problem”, even though they are I guess basically equivalent.
In addition to my other comment here, is there any application of formal verification for complex ETL (Data) pipelines, from the standpoint of enumerating transformations, workflow steps, and states, with less emphasis on temporal soundness?
my first thought was something something dependent types (Idris, Agda), but it also sounds like TS-like structural typing with a Rust-like Result type. proving that every incoming message is either parsed correctly or we return an error seems to be the basic building block. and then every transformation should be other pure functions.
And no. Ada is an imperative language with an expressive type system (especially compared to other imperative languages). But it doesn't require things like mathematical correctness/completeness. You may be thinking of SPARK which is a subset of Ada that uses contracts and a prover to prove the contracts hold.
As far as I am aware this doesn’t relate to much work done by Dijkstra. This is really the Type Theoretic computer science program. It starts at Church and progresses through Curry (inspired by Schoenfinkel), then hits the ‘modern’ academic lineage splitting in many directions. On the computer science side in America Constable and his doctoral students (Robert Harper included), Dana Scott, Strachey, Reynolds, the current CMU proof theory/type theory professors, and in Europe you have the French school with Coquand, Huet, etc from INRIA and the English lineage from Milner.
Obviously the above is a very truncated list and misses out on a huge chunk of formative thinkers, and doesn’t get to the cutting edge modern developments. Additionally, I’m leaving out the Math centric developments and people, like Mike Shulman, Dan Licata, etc.
This is all from research way older than Dijkstra.
It does have something to do with his work because his work extended it. But by that metric, it also has something to do with all of computer science, and a lot of mathematics.
Yes, although Dijkstra was interested in proving programs correct in general, not just in how lambda calculi correspond to logics correspond to categories (a proof technique for program correctness, among other things).
I'm not sure. Dijkstra, despite his somewhat incorrect reputation as a curmudgeonly theorist who preferred pencils to computers, had a notable pragmatic streak. And, as a matter of pragmatism, he identified the close relationship between programs and proofs. There are many such examples in his writings, but here are a few[1][2][3][4]. It's particularly interesting to see how his thinking evolves. EWD288 in particular includes what I view as both good advice and a powerful refutation of a fallacy that is stubbornly popular even today:
Finally, a word or two about a wide-spread superstition, viz. that correctness proofs can only be given if you know exactly what your program has to do, that in real life it is often not completely known what the program has to do and that, therefore, in real life correctness proofs are impractical. The fallacy in this argument is to be found in the confusion between "exact" and "complete": although the program requirements may still be "incomplete", a certain number of broad characteristics will be "exactly" known. The abstract program can see to it that these broad specifications are exactly met, while more detailed aspects of the problem specification are catered for in the lower levels.
I have myself applied this in "real" (aka, I got paid for it by a FAANG) programming by proving the correctness of the overall structure of a service (and verifying it with tests) while leaving implementation details for which requirements were nonexistent or unclear unspecified or underspecified. Since I'm neither as creative nor as clever as Dijkstra was I still like to write tests, but I've found that I can do TDD in a way that meshes cleanly with his program and proof construction style. The end result is I have code that I have both proved correct and that has solid test coverage. The tests are particularly useful for collaboration, because it's just not realistic to expect every team member who touches a piece of code to understand and rederive the appropriate parts of said proof. And as an added bonus, it escapes the common complaint for TDD that the tests get in the way of development. They don't when you only test what needs to be proved!
Edit: I find it amusing how much closer Dijkstra's approach is to what might be called "normal" (Algol-ish) programming compared to the Abstract Nonsense[5] favored by the category theorists. I wouldn't call it any less mathematical though. Dijkstra was very much a mathematical formalist as is clear to anyone who has made a study of his work or life.
> I find it amusing how much closer Dijkstra's approach is to what might be called "normal" (Algol-ish) programming compared to the Abstract Nonsense[5] favored by the category theorists. I wouldn't call it any less mathematical though.
It's true that he mainly used a sequential, imperative notation for describing things, but it seems like later in his life, he preferred functional programming for teaching computer science to students. He petitioned the University of Texas to not switch from teaching it in Haskell to teaching it in Java for this reason.
A fundamental reason for the preference is that functional programs are much more readily appreciated as mathematical objects than imperative ones, so that you can teach what rigorous reasoning about programs amounts to. The additional advantage of functional programming with “lazy evaluation” is that it provides an environment that discourages operational reasoning.
Yes, programming is a super set of theorem proving.
It is true, but not generally useful.
Building useful programmes is, IMO, best described as a craft. It is learnt from other crafters, and improves with practice
Formal methods can be helpful in specific cases but generally speaking writing correct formal specifications is just as hard, or harder, than writing useful, reliable, computer programs
Michael Shulman also wrote about the extension to Homotopical Trinitarianism [2]
For a good summary with links there is [3]
[1] Computational Trinitarinism, https://existentialtype.wordpress.com/2011/03/27/the-holy-tr...
[2] Homotopical Trinitarinism, http://home.sandiego.edu/~shulman/papers/trinity.pdf]
[3] nCatLab, https://ncatlab.org/nlab/show/computational+trilogy