For example, looking at this:
>>> 0 if 1 else 'a'
made me remember one of me older thoughts on this subject, more exactly if the word 2/two from a sentence like this: "there are two apples on the table" should be treated as an Integer or a String. Some would say that we should only treat it as an Integer if we intend to do computations on it (for example adding those 2 apples to another 3 apples), to which I'd ask how come the word's essence (its "transformation" from a String to an Integer) changes depending on the type of action we intend to apply on it? (so to speak, English is not my primary language).
Anyway, things in this domain are damn complicated, and have been so for the last 2500 years at least, starting with Socrates (or was it Plato, in fact?) who was asking about the essence/idea of a table (i.e about its type), i.e. is a table still a table if it only has 3 legs remaining? what about two?, and continuing with Aristotle's categories and closer to our days with Frege and Cantor (somehow the table problem can be connected to the latter's continuum hypothesis, but I digress). So one of my points is that this is not only a computer science problem.
It basically leads to things like this:
The way I like to look at it is that Aristotle's logic principles and all that followed (including the concept of "continuity") is a very nice and especially very usefull Domain Specific Language which helped us, humans, fly rockets to the moon and build iPhones (we wouldn't have computers with NAND gates which would not respect Aristotle's logic principles). But if we go back and re-read Heraclitus, the guy against whom Aristotle fiercely battled, then the concept of "continuity" and "one vs many" concepts become a lot more blurry. But I digress, again, this was supposed to be about computer type theory.
I think the problem is viewing "type" as "essence". Type is all about how we intend to use it, and which invariants we wish to enforce.
Also I am very surprised by such statement from 'the author of a several papers with the word type in the title'.
I also would argue that the design of CPUs such that any sequence of bits could be loaded in any in any register and interpreted depending of the current context, instead of having typed registers is not a shortcoming but a fundamental feature. It makes code small and elegant. There is whole TAOCP to see how.
Indeed, the fact that digital data is fundamentally "untyped" and its meaning depends entirely on interpretation is the entire reason why computers are so powerful and general-purpose.
> People who think they understand something about the theory of programming languages, including me, tend to agree that what Python does is wrong.
> In fact you can program heterogeneous lists in dependently typed languages, but it’s unreasonably complicated.
Here's some Agda code that shows that both heterogeneous if statements and lists make sense and are easy to work with in type theory.
data Bool : Set where
true false : Bool
if : ∀ {ℓ} {P : Bool → Set ℓ} (b : Bool) → P true → P false → P b
if true t f = t
if false t f = f
postulate Char : Set
{-# BUILTIN CHAR Char #-}
data ℕ : Set where
zero : ℕ
suc : ℕ → ℕ
{-# BUILTIN NATURAL ℕ #-}
ex₁ : ℕ
ex₁ = if {P = λ b → if b ℕ Char} true 0 'a'
infixr 5 _∷_
data List {a} (A : Set a) : Set a where
[] : List A
_∷_ : A → List A → List A
data HList : List Set → Set where
[] : HList []
_∷_ : ∀ {A As} → A → HList As → HList (A ∷ As)
ex₂ : HList (ℕ ∷ Char ∷ (ℕ → ℕ) ∷ [])
ex₂ = 1 ∷ 'a' ∷ suc ∷ []Much recent research in programming languages is about types for interacting processes. The most well-known, but by no means only example are the session types pioneered by K. Honda. The key idea is that each process has a bunch of interaction points, and the interaction at each interaction point is constrained by a type. Such types typically
- What direction does data flow? E.g. channel x is used for input while channel y does only output.
- What kind of data is exchanged on the channel? E.g. x is used to exchange a boolean, while y exchanges pairs, the first component of which is a double, and the second component is a channel name which is used for inputing a string.
- How often is a channel used? E.g. x is linear (used exactly once), y is affine (used at most once) while z can be used an arbitrary number of times.
This setup has been investigated from many angles, and one of the most beautiful results in this space is that normal types known from lambda-calculus (e.g. function space) can be recovered precisely as special cases of such interaction types, using Milner's well-known encoding of functional computation as interaction.
One of FP's more annoying achievements is somehow convincing people that it's the only way to make programs more verifiable or more "mathematical". Imperative, stateful computation can be (and is) just as mathematical (whatever that means) and just as verifiable as pure-FP (it must be constrained in some ways, but not as extreme as requiring complete referential-transparency).
It's good to learn about applying types to process calculi. I wasn't aware of that work at all.
Imperative, stateful computation [...] is [...] as [...] verifiable as pure-FP
Exactly. When you verify your programs using some kind of program logic you realise that you have to keep track of all relevant data anyway.The condescension towards languages like C/C++ and Java that some FP extremists have shown is probably one of the reasons for the rift between working programmers and PL theory. Fortunately, things are much better now, with most new languages (e.g. Scala, Rust, Clojure) bridging both worlds.
applying types to process calculi. I wasn't aware of that work at all.
Maybe "A Gentle Introduction to
Multiparty Asynchronous Session Types" http://goo.gl/FeVLv3 could be an introduction?In Coq, co-inductive types are used to model infinite objects like (never-ending) streams or (non-terminating) program executions.
Inductive List (A: Set): Set :=
| Nil: List A
| Cons : A -> List A -> List A.
CoInductive Stream (A: Set): Set :=
| SCons: A -> Stream A -> Stream A.
Fixpoint len {A: Set} (x: List A): nat :=
match x with
| Nil => 0
| Cons _ y => 1 + len y
end.
CoFixpoint ones : Stream nat :=
SCons nat 1 ones.
If you want to talk about how systems interact with the world, you can use bisimilarity relations to prove "these two systems interact with the world in the same way".You can also use co-inductive types to embed the temporal logic of actions (TLA) in Coq; TLA is the language Leslie Lamport works with for "Specifying Systems".
* http://www.labri.fr/perso/casteran/RecTutorial.pdf
* http://www.amazon.com/Specifying-Systems-Language-Hardware-E...
NO NO NO. Stop. It was cute when people got this wrong in 2010; now it's just ignorant to pretend to know enough about Haskell to tell us something about it and still get this so wrong. There is no inherent connection between monads and statefulness; none whatsoever. Monads just happened to be a useful abstraction to make opaque for the purposes of IO.
There was a great quote saying that when you get something almost right but not completely right, you have to explain it to people over and over again. IMO people should pay more attention to that quote, it applies to monads perfectly.
In what sense is the difference "gratuitous"? Seems to me its a pretty significant, meaningful difference and that the two types represent radically different things.
The common type is object, or perhaps more specifically, non-nil object (object!), if 0 is not treated as nil (value type).
If you try to interact with an object (add it to a number, call a method, etc.), your language has to decide what to do: fail because that method is not part of the type object, or force people to cast to the proper sub-type, or allow a kind of dependent typing/casting (if type(it)=int: it+1 else if type=char: it.uppercase), or (like boo, python, ruby, etc.) allow "duck typing": allow any calls/methods on the object, and check it at run-time instead of compile-time
I admit to not being that familiar with type theory (I've done a little Scala but most of my FP experience is in Clojure) but when I read this my gut reaction was: "Isn't that kind of what Go is doing?"
Meaning that in Go, the focus of compositionality isn't on basic types and the functions that act on them (see Go's lack of generics) but rather the focus is on getting different modules to agree on "what should be done to some stuff" using interfaces. This is putting interaction first but also getting compositionality in a "generic" way given that interfaces in Go are implemented implicitly.
But what is so special about Go's approach to interfaces? The only special thing I see is that interfaces are implemented implicitly, which I see more as a "implicit over explicit" decision rather than anything having to do with composition per se. Yet people keep raving about it.
I feel the same way. Uniqueness typing can work as the clue between component and upper lever abstraction.
The benefit of proofs in code would only be worthwhile if it was relatively easy for common programmers to prove the absence of bugs that would have been more costly to find and fix using other methods, and that that ease would not come at the cost of other properties (such as performance). So far I have a feeling that this is not being delivered. Your run-of-the-mill clever use of types usually prevents very "cheap" bugs, and hard bugs are even harder to prevent with types.
I'll give a concrete and recent real-world example. A few months ago, a bug was discovered in Python's and Java's sequential sorting algorithm[1], Timsort. It was introduced to Python in 2002, and its adoption by Java in 2011 has probably made it the most used sorting algorithm in the world. Yet, it contained a fatal bug that could cause it to crash by overflowing an array. There are a few interesting observations one could make about the bug and its discovery with relation to type systems.
1. The bug was discovered by a verification program that does not rely on type-based proofs in the code. The output from the verification system quickly showed where the bug actually was. AFAIK, the verification program required no assistance with proving the correctness of the algorithm, only that the variants the algorithm seeks to maintain be stated.
2. Proving the invariants required by the (rather complicated) algorithm with types would have required dependent types, and would have probably been a lot harder than using the verification program used. It is also unclear (to me) whether the problem and the fix would have been so immediately apparent.
3. The interaction of "type-proofs" with program semantics (namely, the requirement of referential transparency) would have made the type-proven algorithm much slower, and this particular algorithm was chosen just for its speed.
4. The corrected version ended up not being used because it could have adversely affected performance, and it was noticed that the "incorrect" algorithm with a slightly larger internal array would still always produce correct results for the input sizes currently supported in Java.
This example of a purely computational real-world bug shows some more limitations of the type-system approach to verification, I think.
Going back to effects and interactions, pure-FP approaches have a hard time dealing with timing properties. I already gave the same example on another discussion today, but the Esterel language (and languages influenced by it) has been used successfully to write 100% verifiable programs for safety-critical domains in the industry for a long time now. It is an imperative language (though it's not Turing complete) with side effects and a lot of interactions, yet it has a verifier that uses Pnueli's temporal logic to guarantee behavior, without requiring the programmer to supply proofs.
To summarize, type systems have shown their usefulness in reducing the cost of software development, but they need to be considered as a component in a larger verification space, and they need to strike a balance between usefulness and ease of use.
[1]: http://www.envisage-project.eu/proving-android-java-and-pyth...
[2]: http://www.envisage-project.eu/key-deductive-verification-of...
I don't understand why proofs need to be universally easy to write/understand to be useful. Why can't they be useful for a subset of programmers on a subset of projects?
> So far I have a feeling that this is not being delivered. Your run-of-the-mill clever use of types usually prevents very "cheap" bugs, and hard bugs are even harder to prevent with types.
Types can be used to encode complicated invariants of software systems, which will be automatically be machine checked throughout the entire lifetime of your project. There is a vast gradient from simple properties like 'this value is a number' to 'your data migrations are guaranteed to be completely reversible without any information loss' to 'the sequential composition of a set procedures will terminate within 600 cycles'.
Types are not only for preventing bugs. They model invariants, help design and allow easy reasoning over large software projects.
> This example of a purely computational real-world bug shows some more limitations of the type-system approach to verification, I think.
No it doesn't. You just pick one example for which it didn't really work out for some reason. That tells you exactly zero about if type based verification could be useful in the general case. (Or a specific subset of verification cases)
> To summarize, type systems have shown their usefulness in reducing the cost of software development, but they need to be considered as a component in a larger verification space, and they need to strike a balance between usefulness and ease of use.
Of course they need to. Luckily that's what smart people are working on every day, making types easier to use.
They can, but that would hardly make a deep impact on the industry. And my question would then be why do you think they'll be useful only for a subset of programmers and a subset of projects?
> Types can be used to encode complicated invariants of software systems, which will be automatically be machine checked throughout the entire lifetime of your project.
I know they can, but so can other verification mechanisms that are less intrusive, or even pluggable types, like those that have recently been added to Java 8.
One point of difficulty -- that the article is talking about -- is precisely those variants that are really hard to enforce, like "every request X will be handled within 300 ms", or "the server will never respond Y if the user has never asked for Z", or "the system will never contact the database if the operator has turned on the offline flag". Similar (though less sophisticated) kinds of guarantees are made and verified by Esterel, without requiring the programmer to supply the proof. Other approaches include behavioral programming[1] (made by the same people behind the research that had led to Esterel), which simply let you specify a rule: "if offline flag is on, disable calls to the database", which will be enforced no matter how much lower priority rules try to access the database.
Types are just one approach for verification. There are invariants they excel at proving, and those that not so much. People who research type systems should, of course, explore anything that's possible with them, but software developers should realize that types are not the be-all and end-all of writing verifiably correct programs, and that for some program properties there are better approaches than types.
> They model invariants, help design and allow easy reasoning over large software projects.
I agree in theory. But I also think they may help up to a certain point and then start hurting. The problem is, this has never been put to the test.
Related to null, you only get one null. the classic assoc problem. if i lookup a key in a hash, does the hash have the key, or is they value of the key null? Optional/Maybe is just wonderful for this case.
Semantic meaning of common types.
update(String name, String email, String address, ...)
vs update(Name name, Email email, Address address, ...)
Strings get overloaded for lots of uses, and it's so easy to swap the order when you have multiple arguments.Status codes, stuff like Email vs VerifiedEmail. Encoding those random flags in types makes it so much easier to enforce rules.
Yes, they are all "easy", but we've all lost hours or days to those problems. The sooner we can stop worrying about bonehead, forgot to check for null errors, the sooner we can concentrate on real problems. They're trivial problems, but we waste a big fraction of our lives dealing with them. Just taking those issues off the table makes it possible for average programmers to find bugs in timsort. The cognitive load of these dozens of details detracts from average programmers (like me) ability to actually detect the hard cases.
Typing isn't a panacea, but it is a help. It's akin to a socket driver vs a wrench. sockets abstract away the need to remove rotate and refit the wrench for every stinking quarter turn. You can do it with a wrench, but it takes so much longer.
There are no silver bullets. but there are regular bullets, and a good type system is one of them.
My point isn't that types are not helpful -- they are extremely helpful. My point it that on the continuum between no types and "types try to prove everything", there is a point that is the most useful, and that point is probably far from either extreme (I don't know if it's equally far from both, but I think it's pretty far from both).
The program will need to keep track of the types at runtime, but that's also true of Python, so what?
Try to expand the comment. Something like: "Conor McBride has a article that agree/disagree with this [link] because [two or three line explanation]."