Packaging for Smalltalk is in the air. The topic emerged twice today, first at lunch when discussing Pharo with Adrian and Lukas, and later when Toon showed me CaesarJ. In both discussions, I argued that we need method namespaces for Smalltalk.
Class reopening is a well known, that is extending an existing classes with new methods. In Smalltalk classes are typically reopened using a package extension mechanism, whereas Python and Ruby use “monkey patching” (even though modules might be a better solution).
The problems that arise from these approaches are discussed en detail by Gilad Bracha on his blog, I will thus start media res using a simple example. Imagine that two libraries, A and B, both extend the String class with an #asURL method. Two implementations of the same method are provided. Thus the problem: which implementation is used? Or more precise, on what kind of context does the choice among these two implementations depend?
As it is now in Smalltalk, the latter implementation overrides the former, and thus (assumed that the load order is first A then B) the definition of #asURL in B wins and is always used. This is obviously broken, since all code in A and all clients that expect A’s implementation are likely to fail.
As it is suggested in Classboxes, the – take a deep breath – imports of the lexical scope in which the current thread was started decides which version is picked (based on IM conversation with Alex, this was not obvious to me from his thesis). I have never used this solution in practice, but it does not seem to address the problem at hand. First, usage of libraries is typically not separated by threads, that is, we typically want to use both A and B from the same thread. Second, the principle of least surprise is violated, as we can not tell which version is picked just by looking at the code (ie from lexical scope alone).
There is another approach, taken by C#’s extension methods. Each extension method is scoped within its namespace, that said, the #asULR method of A is picked for all calls from the lexical scope of A, and the #asURL method of B is picked for all calls from the lexical scope of B. Whenever some client code wants to call #asURL, it must explicitly import the method either from A or from B. This satisfies both the constraint that neither A nor B break each other or each other’s client code, and the principle of least surprise.
How can this by applied to Smalltalk?
- The name of all compiled methods is prepended with the lexical scope, that is the name of the enclosing package. For example, #A_asURL if the method #asURL is defined by the package A, and vice versa for B.
- The compiler prepends all message sends with the name of the lexical scope of the current method, that is the name of the enclosing package. For example, #A_asURL if #asURL is sent from package A, or #Foo_asURL if #asURL is sent from Client Foo.
- When a client package, for example Foo, imports #asURL from a provider Package, lets say A, a hidden method #Foo_asURL is created that delegates to #A_asURL.
Of course, Browser, Debugger and Inspector should be updated to reflect and properly display this naming convention. For example, a code editor should show the names without prefix, whereas the debugger probably should display the extensions. The latter could also be controlled by a boolean setting. The generated code should run fine in any image.