You're misguided. What you describe is not really an issue for dependent types. Your attitude seems to be shared by a lot of people, so I'll try to clear things up a bit. I think it helps if I use refinement types (as used in F
), which are just dependent types in disguise.Taking your example, I would have written a function f* that takes a list (with an arbitrary type of elements) and produces an integer. Its basic ML type would be:
f : list 'a -> int
Now taking your example, assume that, for whichever reason, f only works for lists of length 5. This would probably never arise in practice, but I'm just going with your example. First, we have to say what "works" means. Intuitively, it means that
f produces a reasonable output. Let's say we have defined a function
p which says whether the output is reasonable. In F
, I can state all of that by giving my function f* the type:
f : l:list 'a{length l = 5} -> x:int{p x = True}
I can only give this type if I can convince the compiler that the result really satisfies p - this is one point I'll get back to later.
Now assume I want to use this function later in my code to deal with some user provided input. Assume I have a function read_int_list:
read_int_list : () -> list int
I can now combine this function with
f, but
only if I ensure that the length of the read list is 5:
val process_input =
let inp = read_inst_list ();
let res = if length inp = 5
then f inp
else -1 // error
This will now be accepted by the compiler, since it will be able to deduce that
inp is an admissible input to
f, because the function call was guarded by the if condition (I'll also get back to this). Note that the code above is absolutely no different than what you would write in any "regular" programming language (including ML). What is different is that in a regular language you could forget to add the condition - but not in F
. Of course, the error handling code above is not sensible.So interacting with user input and output is absolutely NOT a problem for dependently typed languages and their ilk. In fact, there is a full-blown implementation of the TLS protocol in F.
What is a problem, however, are the two points I hinted at: convincing the compiler that your statements are true. This is done in one of the two ways. First, you can manually give "hints" to the compiler, which is normally very tedious. Second, the compiler can try proving them automatically (F* for example leverages an SMT solver), which is great when it works, but often doesn't work.
So the real problem is that producing proofs is quite time consuming, and thus expensive. The world, as you put it, is not completely wrong: very often, a buggy program is cheaper than a correct one. Heck, people still stick to OpenSSL over the above verified TLS implementation - and for good reasons. The exceptions being programs used in NASA rovers and such - which is where you're starting to see dependent types being used. I certainly won't use them for my projects - at least not until the systems get massively better at producing proofs. It's partly happened with the advent of the SMT solvers, but it's nowhere near enough.