Functional Objects and Object-oriented Functions: Purity vs. Impurity in Pursuing Programming Paradigms
In order to write better code, it is probably best not to restrict oneself to a single programming paradigm.
There are some people who believe that functional programming and message-passing-based programming are mutually exclusive, but I do not believe so.
A few years ago, I read a slide-show presentation by Matthias Felleisen of the PLT research group at Northeastern University, entitled “Functional Objects,” presented at the European Conference on Object-Oriented Programming Languages, at Oslo, Norway, in 2004.
In slide 62/74 of the presentation, Felleisen quotes a portion of an e-mail message from Guy [L. Steele, Jr., according to a comment below by Ram], as follows:
Guy in email to me, cc’ed Gerry [Sussman?] on April 2, 2004:
“We decided to construct a toy implementation of an actor language so that we could play with it …
Then came a crucial discovery. Once we got the interpreter working correctly and had played with it for a while, writing small actors programs, we were astonished to discover that that the program fragments in _apply_ that implemented function application and actor invocation were identical!”
What is interesting is that the actor model formed a basis for such message-passing programming languages as Smalltalk. According to the presentation, the implementations of these portions of the toy actor language were identical to the corresponding portions of function application that were also implemented.
This implies that there are common ideas present between functional programming and message-passing-based programming.
In the paper “Teaching Programming Languages in a Post-Linnaean Age,” by Shriram Krishnamurthi of Brown University, presented at the 2008 SIGPLAN Workshop on Programming Language Curriculum, on May 29 to 30, 2008, at Cambridge, MA, USA, Krishnamurthi adamantly opposes the classification of programming languages into paradigms. He writes (page 1, section 1, third paragraph):
Most books rigorously adhere to the sacred division of languages into “functional”, “imperative”, “object-oriented”, and “logic” camps. I conjecture that this desire for taxonomy is an artifact of our science-envy from the early days of our discipline: a misguided attempt to follow the practice of science rather than its spirit.
Instead, he believes that programming languages should be considered as “aggregations of features” (page 2, section 4, first paragraph):
If languages are not defined by taxonomies, how are they constructed? They are aggregations of features. Rather than study extant languages as a whole, which conflates the essential with the accidental, it is more instructive to decompose them into constituent features, which in turn can be studied individually. The student then has a toolkit of features that they can re-compose per their needs.
However, there are also those who insist on purity within a specific paradigm. For example, in a post on the Haskell-Cafe Mailing List entitled “a regressive view of support for imperative programming in Haskell,” by Paul Hudak, dated “Wed Aug 8 14:20:39 EDT 2007,” Hudak writes:
All of the recent talk of support for imperative programming in Haskell makes me really nervous. To be honest, I’ve always been a bit uncomfortable even with monad syntax. Instead of:
[Code highlighting is that of the writer of this article.]
do x <- cmd1
y <- cmd2
I was always perfectly happy with:
cmd1 >>= \x->
cmd2 >>= \y->
Functions are in my comfort zone; syntax that hides them takes me out of my comfort zone.
In my opinion one of the key principles in the design of Haskell has been the insistence on purity. It is arguably what led the Haskell designers to “discover” the monadic solution to IO, and is more generally what inspired many researchers to “discover” purely functional solutions to many seemingly imperative problems. With references and mutable data structures and IO and who-knows-what-else to support the Imperative Way, this discovery process becomes stunted.
In other words, Hudak believes that insisting upon purity leads to “purely functional solutions to many seemingly imperative problems.”
My own view is that the choice of which tools to use should be dictated by the most natural form of expression for the problem. For example, Haskell is designed so that side effects are eschewed; to preserve referential transparency, any mutative behavior must be placed within a monad. Preserving referential transparency can help to prevent bugs by facilitating writing programs for which correctness can relatively easily be proven. In other words, in Haskell, unlike in imperative languages, in many cases, if a program compiles, the program is likely to be correct. Such behavior can be very useful especially for writing functions that are naturally expressed as mathematical functions for which correctness can relatively easily be proven.
However, the static typing system of Haskell precludes true reflectivity. What this means is that the programming language does not fully support all possible modifications to an implementation of the programming language at runtime. In certain cases, it may be necessary to restart a given program in order to reflect changes made to the program if those changes require changes to types.
This may not be the optimal solution for certain types of problems. For example, one pet project that I dream of accomplishing someday is to create a virtual world, modeled on Earth, that supports evolution of the world itself. In other words, I would like to design a virtual world such that in the world, for instance, an entity of type
Monkey can evolve into another entity of type
HumanBeing. Furthermore, the virtual world itself should be able to evolve: For instance, it should be possible for the world to evolve from, say, being of type
AquaticPlanet to type
AquaticPlanet would be more suited to the evolution of aquatic creatures, such as fish, whales, and dolphins, whereas type
TerrestrialPlanet would be better for that of terrestrial creatures, such as elephants, monkeys, and human beings.
However, the model for this virtual world is essentially one of a vast collection of entities exchanging messages (food, by-products, oxygen, carbon dioxide, etc.) among one another. Furthermore, this world can change its own type at runtime. It may be more natural to express this world using a dynamically-typed reflective message-passing programming language, such as Smalltalk, than using a statically-typed referentially transparent purely functional programming language, such as Haskell.
Hence, my conclusion is that the most natural mode of expression for the problem probably should dictate the choice of tools for the solution. Problems that are most naturally expressed mathematically are probably best solved using a referentially transparent functional programming language. Problems that are most naturally expressed as a vast collection of entities exchanging messages among one another while continually changing their own types are probably best solved using a reflective message-passing-based programming language. Problems that lie somewhere in between can probably be solved using a more general-purpose language that is beautiful, elegant, concise, and supports multiple paradigms.
In other words, there is no panacea among programming languages. It is simply not possible to create a single language that is the optimal solution to every possible programming problem. The most natural expression of a problem should dictate the optimal tools for solving the problem.