[This essay was originally written after I spent a few months playing around with ocaml in 2007...]
I don't write enough to have genuine blog, I've decided to write about the experience of using ocaml here. Firstly, I'd like to acknowledge that I'm still in the honeymoon period with OCaml; so keep in mind this isn't a particularly objective critique. That being said, I will try to talk about concrete issues and items, instead of discussing the usual "functional programming will expand your mind", "it'll make you a better programmer even if you don't use it", or other arbitrary issues such as code elegance. This also isn't intended to be an exhaustive list; I'm just trying get you interested enough to start googling around. If I can get at least one child to use OCaml, then it was worth it. ;-)
For some reason, I had never gotten around to anything in the ML family even though I've programmed in a variety of languages. Conventional wisdom says that you can either have an expressive programming language (like python or ruby) or a fast programming language (like C, C++, or assembly). But these guys and gals in the ML world have been saying that you can have the best of both worlds. You can have an high-level language that performs like a systems-level language. It sounded too good to be true. So I decided I was going to learn Objective Caml, hoping that it would have 75% the expressiveness of of python, and 75% the performance of C. In the end, I'd say it has about 50-60% the expressiveness of python, and 90% the performance of C. Keep in mind that you can do some really honestly truely wacky stuff in python, so 60% is still really good.
I wish I could go back in time and retract some statements that I've made in the inevitable dynamic vs. static flame wars. The standard dynamic arguement is that you can prototype faster because you don't have to deal with the write-compile-run loop. You can write code interactively in a shell (a.k.a. REPL), test it out, and when you're done you can copy it into a source file. Well it turns out OCaml has an interactive shell that works just as well as any dynamic language's shell. For me, that interactive shell is the killer 'dynamic' function, and worth more than eval and the ability to modify and replace objects at runtime and all of the other dynamic stuff. Not that those aren't useful, they're just not critical for me.
Also, realistically, the interactivity really only helps at the beginning of a project when you're roughing out ideas and programming in the small. Ultimately (at least for me) even in a dynamic language you end up making changes, running unit tests, and running an application. At that point you're essentially in a write-compile-run loop, even if you're running 'python start_app.py' instead of hitting F5 in Visual Studio. Although you can put in some hooks modify running systems in a dynamic langague, to be honest live-coding on a production system scares the hell out of me!
I always thought of types in newer languages as a hack for lazy compiler writers. That's because most languages used what I'll call (with the completely non-authoritative made-up inaccurate term) 'primitive types'. This is exemplified in Java, where you have int, float, double, and char which lie in some supernatural realm outside of the systems object model. Usually it's done because these can be translated directly to primitives that the CPU can understand and it's more efficient than using some sort of object. Really though, the problems with these types is that you're stuck with whatever the language gives you.
OCaml does types right. For example, if you're dealing with measurements, you can define new types with something like:
type foot = Foot of int;;Then you can create respective values with the constructors:
Foot 1;;Now this was really easy to do, so what's the big deal? Well I can never, never, ever accidentally add/subtract/multiply/devide a foot and a meter, resulting in an invalid measurement calculation. This is what ML programmers refer to as 'type-safety'. Also, I was able to do this easily without jumping through a lot of hoops, and it makes the code more readable. I didn't have to go through some static-typing 'straight-jacket' or perform any static-typing 'S&M' like the old me would've said in a dynamic vs. static flame war.
How do you do this in other 'conventional' languages? Sure, you can use 'typedef' in C, but that's really more of a preprocessor directive. I honestly forget what the compiler does if you mix typedefs with the same underlying primitives (i.e. typdef int Apples;typedef int Oranges; Apples A=0;Oranges B=1;A=B;) but I seem to think you'll only get a warning, if that. And Java doesn't even have typedefs, forcing you to write a bunch of boilerplate class code to simulate the feature.
Similarly, you get union types for free in OCaml. In fact, I'm sure OCaml programmers were groaning at the previous example. It should've been:
type length = Foot of int | Meter of int;;Now you can still use the same constructors, but they have the same type. Note that even though they're the same type, you still can't add a Foot and a Meter accidentally. But because they're the same type you can put them in a list:
[Foot 3;Meter 4;Foot 5];;Once again, doing something like this gets ugly in the C world, you're stuck creating a union type where the first field is an enum. And every single time you use it, you need to check that enum value to make sure you're using it properly! If you forget or decide to cheat just once, you can have (at best) a program with bad results or (at worst) a fatal error. Similarly in Java you would need to come up with some convoluted inheritance hierarchy to try to represent this so you could trick the compiler. Once again leading to accusations of straight-jackets and S&M.
So to summarize, the simplicity of the type system actually makes sense to me as a human. I'm using the type system to make things clearer to me, not to trick the compiler or as a performance optimization.
Okay, in the OCaml world they use the term 'polymorphism' but you're probably used to hearing 'generics' or 'templates'. Here's a normal function in OCaml that simply takes some value and returns it. Yes, it's a stupid function, but it should be easy to understand:
let return_it x = x;;And now here's the polymorphic (a.k.a. generic) version that takes any type:
let return_it x = x;;Did you notice the difference between the two functions? If not, look again. There is no difference! The compiler is smart enough to know that that is generic, and will automatically create the right versions when you feed in different types:
Okay, now for a more concrete example. Suppose I'm writing a little RPN calculator that takes a list of instructions and calculates the result. I liked the fact that in (say) python I could just write a list like:
calculate([1,2,4,5,6,"+","+","+","+",5,"/"])Now in the static guy in my flame war would say, "Yeah, that's nice, but what happens when someone accidentally sends in a list with a file handle in it? The compiler should catch that." To which I would respond, "Well it'll blow up, but (1) it's the dude's own fault for sending in a file object, and (2) writing a static list that takes both numbers and chars is a pain in the ass." Once again because I would have to use a C-style union, or some bizarre inheritance hierarchy plus templates, and write boilerplate code.
But once again, the problem was I wasn't using a type system for humans, and if I could get the compile time safety for free, I'd take it. In OCaml, I could create a union and use that:
type rpn = Number of int | Plus | Minus | Divide | Multiply;;
I get a generic, but type-safe list. That is I can't append a Foot or Meter to the list. And I get it for free. Why wouldn't you want that?
I could probably go on and on, but that's enough for now. As I mentioned earlier, I wanted to learn OCaml to see if arguments about Static vs Dynamic, or Performance vs Productivity are really a false dichotomy. And yes they are. Historical accident is responsible for many people's views on these topics than any immutable laws of comp sci. OCaml's slogan should be "Why Compromise?"
Anyway, if I've managed to get you interested, go ahead and download OCaml. If I were you, I'd also start off reading the Jason Hickey's informal introduction, and then move on to the english translation of OReilly France book for a more exhaustive introduction.