Advanced programming languages (Haskell, Scala, SML, OCaml, Scheme)
matt.might.net
matt.might.net
A difficult position to defend but the reason I end up favoring strictly typed languages over dynamically typed (I'm ambivalent about duck-typed, so Python ends up being my favorite language for other reason). I love Haskell, as you may be able to tell.
I love pythons dynamic types, and I love haskells typesystem, because once you get used to it, it becomes pretty much as painless as having dynamic types.
On the other hand, I utterly hate Javas typesystem, because it tends to be pretty obnoxious and in the way (probably I am not enough of a type theorist to actually understand why this is the case, but I can see that there are a lot of cases in the java type system which are just annoying roadblocks).
Imagine this setup: You use a fully dynamically typed language. You write unit tests and ensure 100% code coverage. You run those unit tests during the build phase.
Now, is there really difference in terms of guarantees that can be given about the app after it compiles? This arguably gives you better guarantee than static typing because it is also protects you from variety of errors such as using one expression of type FOO in place of another expression of the same type.
That sounds like a lot of extra effort; it would be nice to automate the testing a bit. Maybe some sort of simple declarative system to check that the code obeys certain constraints. Ideally, you could make your auto-test system clever enough to actually deduce a lot of the constraints itself, based on the structure of the code--you'd really save time that way! Then you could focus your effort on just testing the things your auto-testing system can't check.
I wonder if anything like that exists?
As to your question, that sounds very much like Haskel. They even have a search engine that lets you search library functions by type signature, and often time you find the right function just based on that.
def f(x, y):
return x + y
and this test function: def test_f():
assertEqual(2, f(1, 1))
There you go, 100% code coverage. Unfortunately we have covered next to 0% of what could possibly go wrong here.If the programmer's intent was mistaken, then the program needs to be re-written. You don't really need unit-tests for that, you just need to think about it. Maybe the unit-tests force you to think while writing them?
There's roughly three places where a programmer can make a mistake--understanding a task to be done, creating an algorithm to perform the task, and describing the algorithm to the computer. Type checks help insure that the algorithm you intended to code is what you actually coded; tests can help ensure that the algorithm you coded actually performs the task you think it does.
"Beware of bugs in the above code; I have only proved it correct, not tried it." -- D. Knuth
Is my Ruby code more concise than my C++? Yep. But the C++ is smaller than Ruby + unit tests.
Let's image this counter scenario: You write a couple unit tests for your most common cases and a couple corner cases. You don't even try for code coverage over ~80%.
For me, the second case is more common. I simply don't have the time to keep the unit tests up to date with every iteration. In these cases I like static typing as it finds errors I miss.
So far as I know in order to use these implementation you have to specify static type information about the test functions. Moreover, I don't think any of the alternative implementations have the flexibility of Haskell QC's Arbitrary typeclass for generating test data.
Obviously anything can be implemented equivalently across any Turing equivalent language. QC is deeply tied to the kinds of power you get from strong static typing, though. I'd be willing to bet that even though it exists on a few other platforms, it is not as useful or easy as it is in Haskell -- perhaps measurable by the average test coverage divided by the time spent writing tests in similarly developed programs.
Click on: Do you miss them?
"In reality when you move beyond very simple properties, then you almost always need to specify generators. Generation is more complex than just saying, give me an int. [...] So as soon as you move beyond very simple properties, you have to write the generators in Haskell also, so that you have to write them in the Erlang version is not a disadvantage."
The difference is that once you start combining Arbitrary instances you need to have strict, careful control over these input spaces, something "trivial" when you're abusing the Hindley-Miller type system. I've never written QC tests in Erlang, let alone at higher levels of complexity, but I can only imagine that it's a short while before you're forced to manually do inference a static type system auto-infers.
?FORALL(N,int(),
?FORALL(M,int(),
N+M==M+N)).
In Haskell you can specify the `int()`'s in the type instead of in the code. What I think Hughes is saying is that for real world testing scenarios, the standard type generators aren't fine grained enough. For example, you need to specify `positiveint()`, or `elementinlist(L)`.That is an absolutely crucial point in languages where side-effects are not controlled.
In my experience, the times that lack of path coverage gets you into the most trouble is when one large-ish function depends in subtle ways on global state, and inevitably there are bugs involving one or two global states that you don't catch in testing, and then you get errors out in production and have to figure out what on earth is causing it. Not fun.
Unit testing and static type checking have some overlap, but it is not complete overlap. Both are desirable.
The difference is that in a program written in statically typed language you'd have to write fewer unit tests than you would were you to use a dynamically typed language, as in the former much of that testing is going to be automatically done for you by the compiler.
And the statically typed program is probably going to contain a lot less code than the dynamic program, especially if you consider all the extra code you'd need to write in those unit tests which simply duplicate the guarantees that you get for free when you use the static language's compiler.
Which is of course ludicrous--no one would create a static-typed language that made you code that way; it'd be practically unusable.
My own experience with some of these languages has been that when the compiler complains, it's because of a bug in the program, not because it can't figure out a type without annotation. So, a statically typed language's compiler becomes, in effect, a bug detector (to a much greater extent than a dynamically typed language's compiler or interpreter can be).
Having such a bug detector can be worth its weight in gold. But just how useful it is depends on how good the error messages are (or how good the programmer is at deciphering them). Unfortunately, this is an area that leaves a lot to be desired. But if you're seriously concerned about safety, statically typed languages (with all of their shortfalls) are still clearly the way to go.
I was actually hacking on some Haskell just now, in fact, trying to fix a "bug". ("Couldn't match expected type `ProgrammerThatUnderstandsMonads' against inferred type `Newbie'")
Duck-typing may save quite a bit of code when dealing with generic functions without the complexity of a sophisticated type system (See "Scrap your boilerplate" for the Haskell approach).
Given the choice, I know which I'd choose!
In practise it's not quite that simple, but designing around the types and shifting the burden of testing on to the compiler seems like the way forward.
Still, given all that, Erlang would be on my list. :)
I actually found it well-written compared to other such articles that have been linked here before. Although it should have been called "Functional languages every programmer should know."
My personal list would include: Erlang, Forth, Scheme, Haskell, Prolog and omit Scala and *ML.
Why Clojure when Haskell and Scheme have already been mentioned? I think the latter two are more educative than the former.