The more succinct a language is, the more subtle it is to design usable APIs, conforming to the best practices of the language. And it becomes increasingly difficult when the language starts offering more and more intellectual surface area along multiple orthogonal axes of variability. You need to think in terms of adapting your APIs to all the axes and try to make them extensible across all of them.
Erlang is one language that makes you think in at least one additional dimension while designing new functionalities. Being a functional language, dynamically typed, you need to think of all the usual stuff (as mentioned above) to come up with an extensible design of your module. Like other functional languages, functions are the basic units of abstraction in Erlang. But there is also a dynamic view of how functions live within processes in Erlang. Functions are static entities that get their lives when passed around in graphs of linked processes spawned by the Erlang virtual machine. Just as in an OO language like Java, you have objects as the means to encapsulate state and identity of the abstraction, in Erlang you design processes that provide encapsulation of states through published message interfaces. And processes can run anywhere - in your local machine, in another virtual node in the same machine, in another machine on the same subnet / domain, or in some place else anywhere within the internet.
When you design APIs in Erlang, you need to think of distribution and concurrency as well. This is because transparency of distribution is baked into the language itself. And your module needs to be compliant with all the characteristics of a distributed ecosystem.
Designing APIs in Erlang needs you to make the additional consideration for transparent distribution.
I was going through this thread in the Erlang mailing list over the last weekend. The thread discusses alternatives for designing a mocking framework in Erlang to improve testability of Erlang programs.
In Erlang the basic unit of execution is a process, which is supposed to be an ultra-lightweight abstraction that can be spawned in millions on your commodity hardware. A typical program uses spawning of processes as ..
Pid = spawn_link(fun banana:loop/0).
Erlang supports hot code swapping, whereby you can change your code and immediately test impact of your code changes, without recompilation and without a millisecond of server downtime. The running process receives new messages on the fly, loads the new code and manages multiple versions simultaneously - a feature of the Erlang virtual machine that is fairly unique, and will remain so, till we see the OSGi stuff maturing into a comparable offering.
But the problem with the above is that the function name is hardcoded while spawning the process. Here the function name acts as the interface that scaffolds native Erlang message processing within it. Hot swapping of code is possible by sending new messages to processes and the VM will allow dynamic loading of the new code to perform the new task. But the interface module hiding the message processing makes it impossible to change the function name and replace it with a mock.
One approach discussed is to purge the existing real
bananamodule and replace it with the mock using
code:load_abs/1. However that will not allow a generic enough mock and will have to be another
banana. And the approach will suffer from the downside that tests can't be run safely in parallel with the real module in the same node, since module namespace is node-wide. Finally Christian suggested implementing a module having an interface similar to
undefined_lambdato set the mock module into process dictionary and load it on error (similar to
method_missingof Ruby). Have a look at the thread for the gory details ..
Anyway, this post is not about hot code swapping in Erlang. The interesting bit about the above mentioned thread is how distribution-transparency features as a concern in a discussion that apparently does not seem to be related to mocking abstractions to improve testability.
In languages like Java, distribution is a totally extraneous concern to the language features and is addressed as a separate deployment concern using grid computing frameworks. In some respect, the POJO model of Java development shines in the fact that you need to consider only the domain problem at hand viz. mocking, in this case. Additional frameworks take care of distribution and concurrency issues - a good example of separation of concerns. But, at the same time, introducing new frameworks has its own downsides, the impedance mismatch that it brings and makes distribution concerns look like bolted from outside. With Erlang, it is a unified thought process, since the language is designed for distribution and reliability, and with a little thinking ahead, you can design robust, distributable frameworks *only* with the help of the native language features and libraries.
Still thinking, which one is better ..