I am in general deeply skeptical of gradual typing, partially from my own underwhelming experiences with MyPy, and partially because 99% of the time when I hear someone advocating for them, they (1) take as given that dynamic typing makes for more productivity (which does not reflect my own experience), and therefore don't try to justify the claim at all, and (2) they say what gradually types give you is not having to annotate things up front. Types have not required manual annotations since at least 1978 (the original hindley-milner type system which, I will note, while not new, is still more recent than prolog).
TFA does all of the above, plus a parenthetical saying "inference is addressed in the next section" -- and then does not actually address inference later in the post.
I'd much rather have type inference than optional typing.
The argument for dynamic typing (whether one agrees with it or not, I actually don't) has nothing to do with explicit annotations. Rather, it is that there are paths in the design space that lead to correct solutions but pass through several inconsistent attempts (i.e., containing parts that contradicting each other and/or the problem statement). It is useful (again, according to proponents, not me) to consider these inconsistent attempts valid programs so that programmers can obtain example-based feedback about the consequences of their designs. Logic and abstract reasoning are not everyone's forte, after all.
I'd make yet a different account of the benefit of dynamic typing. My reasoning skills exceed that of my compiler and I can thus determine that certain designs are type-safe that my compiler cannot. This means that in static languages I end up either 1) spending a lot of time convincing the compiler my design is type-safe, 2) am restricted in the designs I can choose 3) casting types losing the advantage of static typing. With dynamic typing I'm free to just write the code.
Having said that, I now really like Rust which goes all the way in the other direction.
> My reasoning skills exceed that of my compiler...
Well, your reasoning skills are different from your compiler. You can see things that the compiler can't. But the compiler can also see things that you don't until the compiler points them out to you.
> ... and I can thus determine that certain designs are type-safe that my compiler cannot.
True. But your compiler can also see that certain things are not type-safe that you think are.
Personally, I'm inclined to something like "the human can over-ride the compiler, but only after listening to it.
> Well, your reasoning skills are different from your compiler.
Very true.
> Personally, I'm inclined to something like "the human can over-ride the compiler, but only after listening to it.
For me, it depends on the language. If you have generics, macros, and non-nullable types: I'm on board. If you lack any one of those, I'd rather be dynamic. I don't want to be constantly overriding the compiler, writing a lot of boilerplate, or still have to deal with nulls.
This is a very good argument against mandatory automatic static checks, so long as they are replaced with equally mandatory manual proofs, included in the program's comments and/or documentation. But it does not justify dynamic typing in any way. For example, most programming languages offer no way to statically verify array indexing operations, so I just get my hands dirty and prove my array indices correct the old fashioned way (brain, paper and pen). But runtime bounds checking still annoys me, because they branch on a bit whose value I know beforehand, creating an unreachable control flow path.
Theorem: Whenever `numbers[index]` is used, `0 <= index < numbers.length`.
Proof: Use the loop invariant `0 <= index <= numbers.length`. The invariant holds before the loop begins because `index == 0` (established by you) and `numbers.length >= 0` (established by the implementor of the array type) imply it.
Inductively assume that prior iterations of the loop preserved the invariant and didn't perform any invalid indexing operations. Now, every time the loop's condition is evaluated, there are two possibilities: either `0 <= index == numbers.length` or `0 <= index < numbers.length`. In the former case, the loop terminates and no further indexing operations are used, establishing the claim. In the latter case, `numbers[index]` is a valid indexing operation, and the subsequent `index++` operation does not destroy the invariant, because, given the precondition `0 <= index < numbers.length`, the operation `index++` establishes the postcondition `0 < index <= numbers.length`, which implies the invariant.
---
Note, however, that in this case it would have been more efficient to use an iterator, because the iterator's implementation already packages this proof in a reusable way, so that you don't need to reprove it every time you write a loop similar to this.
Well, I've never seen anyone write a proof for a function so trivial. Instead, most people will simply deem the proof to be sufficiently obvious they don't need to write one out. I've only ever seen someone offer something in terms of a proof for more complex situation where its decidely non-intuitive that array indexes will not go out of bounds.
You can only deem it obvious if you can actually prove it.
---
@winstonewert
> True, but the key word is "can". I could write a proof that my dynamically typed programs are correct
If you could actually write the proof, then you would not want the dynamic checks, as all they offer is protection against errors that you can prove you have not made.
> or that my functions do not index out of bounds, but I don't.
Are you actually sure you can?
---
@AnimalMuppet
> By a certain point, you've seen enough of them that you don't have to prove it, you can just see it.
If all your loops are so similar to each other that you can “just see” that an iteration pattern is correct, you should consider writing an iterator library, so that others can benefit from your hard-earned wisdom without going through the pain themselves.
Or else, if you are implying that you could be given an arbitrary loop and “just see” that it is correct, I am afraid you are wrong.
> But of course, everybody thinks they're at that point well before they actually are...
> If you could actually write the proof, then you would not want the dynamic checks, as all they offer is protection against errors that you can prove you have not made.
Even if I write a formal proof, I'm not guaranteed that my program won't index out of bounds. Formals proofs have errors in them all the time. I want dynamic checks to catch me in those cases.
Further to the point, let me reiterate: programmers do not (in almost all cases) write these proofs you are talking about.
Yes! This is the nice moment when the other party in the debate starts to back off from their original position, which, I shall remind you, was:
> My reasoning skills exceed that of my compiler and I can thus determine that certain designs are type-safe that my compiler cannot.
Anyhow. Back to your last comment:
> Even if I write a formal proof, I'm not guaranteed that my program won't index out of bounds.
If you actually come up with a proof, you are completely guaranteed that the proven statement is true.
> Formals proofs have errors in them all the time.
Correction: Purported proofs often actually aren't proofs. It is not a proof if it is wrong.
> I want dynamic checks to catch me in those cases.
Thanks for making my point for me! Self-quotes are decidedly not tasteful, but this situation calls for a reminder:
> It is useful (again, according to proponents, not me) to consider these inconsistent attempts valid programs so that programmers can obtain example-based feedback about the consequences of their designs. Logic and abstract reasoning are not everyone's forte, after all.
Anyhow. Back to your last comment:
> Further to the point, let me reiterate: programmers do not (in almost all cases) write these proofs you are talking about.
This is just a statement of fact, which is true, indeed. But it doesn't support your original position in any way.
We've asked you before not to be uncivil on HN or take it into tedious, repetitive flamewars like this one. Not only are you still doing it, it looks like you're using HN exclusively for it. That's not what this site is for. Since you can't or won't stop, I've banned your account.
The purpose of HN is to gratify intellectual curiosity, not smite readers with the same harangues over and over again. If you don't want to be banned, you're welcome to email hn@ycombinator.com and give us reason to believe that you'll use the site as intended in the future.
> Correction: Purported formal proofs often actually aren't formal proofs. It is not a proof if it is wrong.
Yes, in the strictest sense an incorrect proof is not a proof. But at the same time, people often call either flawed proofs or purported proofs formal proofs in a looser sense. You cannot be unaware of this point. You must have understood what was being conveyed. Yet, instead you decided to engage in a nitpick over the meaning of the phrase "formal proof"
Furthermore, your claim that "If you actually come up with a proof, you are completely guaranteed that the proven statement is true." is utterly unhelpful. You know that my point was that a manually verified proof might still be wrong. And yet rather than addressing that point you decide to evade it by an ill-considered nit pick on whether an incorrect proof is still a proof.
So at this point, you are not discussing the questions in good faith, and I'm done with this conversation.
> But at the same time, people often call either flawed proofs or purported proofs formal proofs in a looser sense.
They are wrong. A purported proof is only a proof if it is actually correct. If you cannot reliably come up with a proof, not mere purported proofs, then just be honest and say so. We are not in a consultant-client relationship, so you gain nothing by lying about your skills to me.
> You know that my point was that a manually verified proof might still be wrong.
Only if you cannot reliably come up with correct proofs.
> And yet rather than addressing that point you decide to evade it by an ill-considered nit pick on whether an incorrect proof is still a proof.
It is not an “ill-considered nitpick”. It is essential to my point. If you can prove that your assertions hold, you do not need to check them dynamically. (Of course, this is logically a triviality, and it is awful that I need to explicitly make this point.)
True, but the key word is "can". I could write a proof that my dynamically typed programs are correct or that my functions do not index out of bounds, but I don't. Nor has any programmer I've ever worked with. Nor has any open source project I've ever seen provide such proofs. People do not write these imaginary proofs, because it would be a waste of time.
I disagree. I've been programming professionally for 33 years. I may have written one wrongly-indexed "for" loop in the last 10 years. By a certain point, you've seen enough of them that you don't have to prove it, you can just see it.
But of course, everybody thinks they're at that point well before they actually are...
Yeah, that's an actually reasonable argument (I don't buy it either, but it's coherent enough). But I honestly think most of the time people aren't making that argument, they're just complaining about the typed languages they know -- more often than not Java.
Apologies, I forgot to follow up on the promised inference discussion in the original version of the post. I just added a paragraph at the end on my thoughts.
> Another big question in gradual programming is inference vs. annotation. As our compilers get smarter, it becomes easier for information like types, lifetimes, and so on to be inferred by the compiler, even if not explicitly annotated by the programmer. However, inference engines are rarely perfect, and when they don't work, every inference-based language feature (to my knowledge) will require an explicit annotation from the user, as opposed to simple inserting the appropriate dynamic checks. In the limit, I envision gradual systems have three modes of operation: for any particular kind of program information, e.g. a type, it is either explicitly annotated, inferred, or deferred to runtime. This is in itself an interesting HCI question--how can people most effectively program in a system where an omitted annotation may or may not be inferred? How does that impact usability, performance, and correctness? This will likely be another important avenue of research for gradual programming.
This is to say, I don't think inference and gradual systems are necessarily at odds, but could potentially work together, although that doesn't happen today.
Additionally, I didn't want to dive into dynamic vs. static typing at risk of inflaming the Great War, but the point I was trying to make was moreso empirical. A huge number of programmers have used dynamic languages to build large systems in the last two decades, which suggests that there is _some_ benefit, although further research is needed to precisely quantify the benefit of dynamic languages.
> This is to say, I don't think inference and gradual systems are necessarily at odds, but could potentially work together, although that doesn't happen today.
Type inference demands some rigidity in the type structure of a language. You must have the right amount of polymorphism: the STLC has too little, System F has too much. If you want both type operators and type synonyms, then operators can't be applied to partially applied synonyms [0], because higher-order unification is undecidable. If you want subtyping, you also have to do it in a certain way [1]. More generally, the type language must have a nice enough algebraic structure to make it feasible to solve systems of type (in)equations, because that's how inference works.
Your post contains a link to a paper [2] that contains the following passage:
> In this paper, we present a way to achieve efficient sound gradual typing. The core component of this approach is a nominal type system with run-time type information, which lets us verify assumptions about many large data structures with a single, quick check.
I have no way to confirm this claim at the moment, but it seems entirely reasonable. Checking structural types requires doing more work. But it is this very structure that makes type inference possible and useful.
So, yes, it seems that inference and gradual typing will be difficult to reconcile.
TFA does all of the above, plus a parenthetical saying "inference is addressed in the next section" -- and then does not actually address inference later in the post.
I'd much rather have type inference than optional typing.