Earlier this week Robert Fischer presented a session at the local Ruby group called "The OCaml Guide for Rubyists" (slides and video available soon). During Q&A, the last question was a simple, "Why?" Not a question so much as a lamentation. The real question being asked was, "Why in the world would you want to put up with horrible syntax, bizarre compiler errors, and unreadable type system REPL output just to accomplish the same thing I do regularly with a few lines of Ruby?" Robert's short answer was, "It will warp your mind," which might not have convinced anyone that OCaml was worth their time. I'd have given the same answer, though, only I would have made my response long, meandering, and possibly in need of a good edit. Welcome and let the journey begin!
I love programming because of the abstraction. Finding and removing repetition is a joyful act. In my C days (which were thankfully short, for both me and my customers), the abstraction came from carefully defined modules. The pinnacle of design was a finely crafted global library accessible to anyone who cared to include the proper header and debug the resulting link error. Entities and concepts were modeled in their own .c files, hierarchies and relationships were defined through directory structures, and exported abstractions made available in .lib files. Oh yeah, I really had this down, and I knew what programming was all about (including spending days finding memory leaks).
And when someone showed me C++ a lightbulb turned on. My basic unit of abstraction changed from the library to inheritence, and my mind was warped. Holy cow! Modeling everything as parent-child relationships opened up a whole new realm of software design. Everything is an object, and everything has shared code in its parent type. I could get wide-spread behavior changes in my program simply by changing some code way up high in the type hierarchy. And the way to do this was multiple inheritance, right? All my systems looked like inverted pyramids... there was one core implementation class with dozens of parent classes spread out above it. Genius, or so I thought.
And then I found out about the Gang of Four Design Patterns. In an act of supreme hubris, I remember arguing with the team architect about the definition of an interface. I firmly believed an interface was an interaction library, like the Win32 Application Programming Interface. I had no idea why anyone would create a parent type with no implementation, and thank you Gavin Ford (wherever you are) for handing me that book and telling me to bugger off. With design patterns and a new found mandate to prefer composition over inheritance, I was off and running with a primary unit of abstraction: the class. Mind warp #2, for those counting. I could never go back to C now.
And for a long time it stopped. Java and its awesome toolset let me create reams and reams of code all structured to one design pattern or another. And the Java libraries contained many of the patterns, so it must be the right way to design software. Java enabled me to overuse patterns, and I slowly realized that they weren't an end to themselves... but this didn't really qualify as a mind warp despite the hundreds of Internet posts proclaiming the death of patterns due to closures and dynamic typing.
People moved on. Some moved on to embrace meta-programming as the next mind warp. But is this really a means of abstraction? Does meta-programming allow you to construct more general and reusable structures? From a client perspective, writing code against ActiveRecord and GORM is certainly a time saving shortcut. But in my experience, meta-programming exposes you (as a writer) to a very low level of the language. Method dispatch, metaclasses, caching closures... this doesn't feel very abstract. It feels reusable as long as method calls and database columns and xml schema all line up. (Quick quiz, what's the difference between programming by convention and coincidence? One is the next big thing and the other is warned against in the Prag book.) But is metaprogramming layer of abstraction that allows you to specify behavior in a more general way? Perhaps not. It's a mind warp, but a very implementation specific warp.
Another direction people move towards is device development, most notably the iPhone. I spent waaay too long programming mobile devices and I cringe at the thought of downloading some iPhone SDK. Honing my programming skills towards learning a device API and graphics library is simply the wrong direction. It is a step closer to the hardware, the implementation, and a step away from a higher level of abstraction. No thanks. I'm quite skeptical that new software ideas will be the product of anyone moving into the device development space. Rediscovering pointer arithmetic and reimplementing existing frameworks on a smaller footprint seems a more likely outcome. Neither of which sounds appealing.
So I'm here to say that mindwarp #3 is discovering the function as the basic unit of abstraction. Jaw-droppingly beautiful abstractions and generalizations can be created out of just functions. You can rediscover the usefulness of partial functions and currying, which were techniques created in the 1800s. You can be in the direct lineage of Alan Turing, who used higher order functions in the 1930s to define his theoretical Turing Machine in his paper "On Computable Numbers, with an Application to the Entscheidungsproblem." And you can finally understand recursion in a deep and intuitive way, and you'll feel like you've looked into the abyss and somehow come back to tell everyone else about it. And maybe, just maybe, you can explain to me what a freakin' monad is.
And maybe (just maybe!) Ruby isn't the language to try this in. Ruby is awesome for a lot of things. Just check out how fast and furious one-line responses to Cedric's challenge appeared. The fact the Ruby solutions are denigrated as "a dime a dozen" says something about how fast and easy the language is to work in. But how easy are function objects in Ruby? Does it take 5 pages of text to fully explain the fine points of procs and blocks? I've been told several times to stop trying to do FP in Groovy because a better functional programming language will more easily reveal the beauty and simplicity of functions. You want to concentrate on the FP concepts and eliminate all the noise of how the language implements functions. Non-functional languages contain imperative and procedural noise that will distract you from having your mind warped. Just pick a more functional language.
And that is my answer of why Rubyists should learn OCaml: Your mind will warp when you discover functions are the next unit of abstraction, and OCaml's sort-of FP purity will have your mind warping sooner than it would with other languages.
Thus concludes my contribution to a well covered topic. Interested readers may wish to read the original Why Functional Programming Matters and Raganwald's thoughts on the same.
Thanks!
Thursday, July 3, 2008
Why Functional Programming?
Subscribe to:
Post Comments (Atom)
13 comments:
Thank you very much for trying to tackle what I have so much trouble expressing.
I wish I could just make everyone code in a functional style long enough for the light-bulb to come on. It's hard to explain what that experience teaches.
One of the things that you didn't really touch on is the power of types. And by "types", I don't mean a concept synonymous with "objects" -- I mean "types" like variant types, abstract types, phantom types, and the like. By thinking in types, you basically brush details under the rug, but then handle those details safely later on when you're ready to. Being able to trust the compiler to check your handling of those types allows you to build high-level and very deep sets of abstractions.
Of course, demonstrating that power -- the way in which it limits your scope for unit tests, eases development on large projects, and makes it easier to reason about code or work with frameworks/libraries -- is hard to do in a presentation. I wish I knew how to.
Great article - thanks for sharing your first-hand experience with this. I'm early on the functional programming path, but even the beginning has been helpful for my day job writing Ruby code.
Also, does anyone really understand monads? :)
@marijn
That's exactly why I don't recommend Haskell to people. OCaml is a much more pragmatic language and allows you to grow into the paradigm.
For learning something new, I'd recommend Haskell rather than OCaml -- the community is more alive, and its neurotic, beautiful purity will require you to reach some degree of enlightenment before you can even manage to write real program.
(Above post was removed because I made a dumb spelling error and it seems one can't edit comments.)
I also recommend Haskell over OCaml for learning. Its purity is a much more pragmatic approach, teaching the student the essence of composing smaller units of software to make larger ones.
You just describe the paradigm shift from procedural to object-oriented to functional programming, that you have underwent. But there's no justification, why one is better, than the other. Moreover, is there really a superior one, or, to paraphrase that, is there a silver bullet? ;)
Excellent article! OCaml is nice (though I prefer the slightly purer SML), but coming from a Java (or even Ruby) background it seems alien and incomprehensible. As far as functional languages with Java roots go, Scala is probably the best one I've ever seen. Actually, Scala is probably the most elegant language I've ever seen...period. (even more than Lisp) Probably its own downside is a less-powerful type inference, which is a questionable "disadvantage" at that.
Having seen new students attempt to learn Haskell in a Comp Sci 101 class (it happened in my degree, but since I had previous programming experience I was able to grasp it), I would not recommend it for learning. Brains explode.
We could probably spend all day arguing over which language would be better to teach to new programmers. I will throw Scheme out there due to its simplicity and the fact that I've seen people exposed to both Haskell and Scheme and find Scheme easier. But somebody will probably reject that too, with some good reason why their language is better than Scheme. Let's face it, there probably isn't a best language for teaching.
@Daniel: While I wouldn't agree on the elegance part - primarily due to its current dependence on the Java libraries for I/O which are not elegant, I do really like Scala. I would recommend it, except that I can't seem to find any books on it! I've seen that they exist, I just can't figure out how to get them.
@IllegalCharacter - As you point out... finding good books on a language is important. I'm apt to recommend Scheme imply because of Structure and Interpretation of Computer Programs (SICP) and the Little Schemer (which is available for other languages like ML). However... I've never done ML or Haskell, so that it probably the /real/ reason I can't recommend them.
@Vsevolod - great question, which one is better. If I had stuck with C from the start and tried to be a better C programmer then would I be blogging now about how great C is? Maybe. I like FP because of the higher level abstraction and that I feel like I'm learning. That is enough of a justification for me.
@Daniel -- another Java based functional language is CAL (http://openquark.org). It's a lazy, pure functional language with HM type inference and a syntax similar to Haskell, with a bit less sugar. I really enjoy using it. I will one day make a serious attempt to learn Scala, but at first glance I don't like it's multi paradigm, kitchen sink look...
If you haven't, you should go check out the Reddit comments on this post, particularly this thread which contrasts Haskell/OCaml.
Post a Comment