Deprecating code is quite common practice. You want to move forward, fix bad code and improve your API. However this can easily become daunting task, because there may be a lot of code to change, or you simply don’t want to break code of other people. Ideally you would like to have some automatic way to change code that can be used both by you, and users of your library. So let’s explore how this can be done in Pharo Smalltalk! I will demonstrate it on deprecating couple of methods in Roassal visualization library.
Imagine that you want to deprecate a method. In Roassal we would like to deprecate all methods
RTEdge class>>buildEdges*, and later replace them with
That’s easy, you just add
#deprecated: to your method and give it some description.
Now every time someone calls this method a warning will appear:
If you want to disable the warning popup, you can execute:
This will disable the warning, but the information is still available in Transcript (Tools > Transcript).
Now we have quite a lot of deprecated methods:
That’s a lot of methods. Just finding all the code that uses it one method at a time is a lot of work, let alone fixing it. So how do we tackle this Pharo way?
In Smalltalk, everything is an Object
String? Object. Number? Object. Class? Object. Method? Object.
What does this mean practically? It means that we can simply ask a class for all its methods and filter out the deprecated ones.
So let’s open up Playground and inspect (Do it all and go) all the methods of the class (both normal and deprecated).
You can then click in the inspector on one of the methods to see its details.
It shows that methods are actually instances of
CompiledMethod. If you browse the CompiledMethods class, you can see many interesting methods such as
#sourceCode, but most importantly
#isDeprecated. (I recommend also looking as how the method is implemented; it surprised me, and it might surprise you. Hint: source code is also object.)
So, now we can simply filter the methods we want.
#isDeprecated is same as sending
[ :each | each isDeprecated ]).
But we are not really interested in those methods, we want the code that sends them. So how we find them? Method is an object, so we ask it for its senders.
Combine it all together and we have senders of all our deprecated methods.
(I’ve removed all empty results and ignored
RTEdge senders (because they are themselves already deprecated)).
Clicking through the inspector you can again quickly navigate to the sender method. In fact you can change the code directly in the inspector!
Unfortunately there’s quite a lot of methods that needs to be changed. Doing that by hand is laborious, repetitive and error-prone task. So ideally you would like some tool that does it automatically for you. In the saved time you can write a blog post. :) It’s time for Rewrite Tool!
Rewrite Tool is a brand new tool that allows you to create rules for rewriting your code. It has won third place in ESUG 2015 Innovation Technology Awards, so I’m definitely not the only person impressed by it. You can watch a demo here http://myfuncoding.blogspot.cz/2015/02/video-presentation-of-rewrite-tool.html
(I’ll skip installation instruction, since you can find it in the link above. (Or just use Pharo’s Catalog Browser.))
So how can we use it for our needs?
Let’s start by opening it via Tools > RewriteRuleBuilder.
In bottom half you write templates for both original and target code. An input (in top left corner) is then transformed according to the templates, and the output ends up in top right corner.
So let’s put in some code of what we want to change and how we want to change it (don’t forget to press ‘’ctrl+s’’ (or ‘’
Use ``@someLabel syntax to identify variables in the transformation rule. The top right corner is automatically generated, so we can see how the input has transformed.
Now let’s press Generate rule and name it RTEdgeDeprecation. This will create a new class
RTEdgeDeprecation in RewriteRulesCustom package.
Finally let’s apply this newly created rule to an actual class.
Open Tools > RewriteRuleBrowser
Then select package and class(es) you want to change. (You can select multiple classes with shift, or right mouse click)
In my case I’ve selected Roassal2 package and RTGeneralExample class, because we’ve identified it earlier as one of the senders.
In the bottom toolbar check “Only custom rules” to see only your rules and select rule you want to apply.
And at last click Apply rule. This will open Changes Browser so you can review all the changes to the code that are about to happen. Note that this will also apply Pharo’s automatic formatting (which is usually a good thing).
With the described approach we can easily define new rewrite rules and change code in couple of clicks.
If you are maintainer of a library, you can even publish the rewrite rules alongside your code, so other people can migrate with ease.
But we are in Smalltalk, so why apply the rules manually when we can script it?
Rewrite Tool is a new tool and currently doesn’t support any direct way for applying multiple rules programmatically. However in Pharo that should not stop you; the source code is always available and ready to be explored, so you should always at least try to find a way. Even if you don’t succeed, you will learn a lot about the tool, system and Pharo.
You know that you can apply a rule through UI of RewriteRuleBrowser (Apply rule button mentioned earlier), so let’s find a class of similar (or in fact same) name. From there we can see couple of interesting methods:
They are just couple lines of code long and seem to provide all we need, so copy what you need into Playground… a good trick is to replace methods with blocks of similar name.
It’s missing just two more things: the rules and the classes.
All rules I’ve created begins with RTEdgeRewrite, and it appears that they are all subclasses of RBTransformationRule. Since class is also an object, we can ask the parent class for it’s children and get what we want.
To get the classes you want to transform, extend the code for retrieving senders (cf. beginning):
Now put it all together and execute your grandiose script. Review your changes and you are done!
As with everything, learning things and doing things for the first time takes more time and effort. By the end it was faster for me to create rewrite rule than change code manually even if there were just two senders. Also I’ve asked Mark Rizun, the creator of Rewrite Tool for couple of things, so doing mass rewrites and rule editing should be easier in the future.