6. Pattern matching in Pharo.
Example
6
“matching part”
``@a > ``@b
ifTrue: [ ``@a ]
ifFalse: [ ``@b ]
“transforming part”
``@a max: ``@b
“input”
a > b
ifTrue: [ a ]
ifFalse: [ b ]
“result”
a max: b
7. The Rewrite Engine7
1. AST focused.
2. Sometimes confusing.
3. Patterns are not composable.
4. Debug and inspect are not
user-friendly.
5. Not extendable.
1. Efficient source
code rewriting.
2. Smalltalk style
syntax.
Good day, everyone. I am Markiyan Rizun and today I would like to present a new pattern library, which is able to match and transform any object in Pharo.
Originally, pattern matching facilities were introduced in functional programming and mainly used for inductive function definitions.
For example, consider shown function written in Haskell, which returns Nth Fibonacci number. The function is implemented used pattern matching. Fib is a pattern that matches an integer n and depending on its value, certain action is executed. If n is 0 or 1 function returns 1, otherwise pattern matches recursively.
Later, pattern matching was adapted to object-oriented world and integrated into many languages such as Newspeak and Scala. In OOP, the principle is to decompose object and to find parts of interest.
Pattern matching facilities already exist in Pharo. We have The Rewrite Engine that focuses on source code rewriting, i.e., it specifically matches nodes in the Abstract Syntax Tree (AST) of Pharo, and we currently use it to refactor code.
Consider the following example. Here we want to rewrite ifTrue:ifFalse: statement into max: message send, which is much better implementation of the same behavior. In order to do that we should apply the following pattern to the source code. We can clearly see that it is very similar to actual Pharo code and has the syntax that uses only few new symbols.
The Rewrite Engine is an effective tool for code rewriting. What is even more, it enables us to create matching and transforming patterns with syntax that is as close to Smalltalk code as possible. The Rewrite Engine introduces only few new symbols.
However, it has its limits. First, The Rewrite Engine works only with current AST implementation that we have in Pharo. Unfortunately, it is not possible to rewrite any other objects than ASTs. In addition, the simplicity of syntax and its similarity to Smalltalk code has its downside – the patterns are sometimes confusing. It is not always clear how certain pattern will behave when applied to specific AST. Moreover, the patterns implemented with The Rewrite Engine does not support composition. Also, debug and inspect with patterns is not very user-friendly. Finally, The Rewrite Engine itself is not extendable.
To overcome the limits of The Rewrite Engine, we propose Phorms. Phorms is a general pattern library that is able to match not only ASTs, but also any other object in Pharo. Additionally, our library enables users to operate patterns like any other first-class objects; therefore, we compose, inspect, and debug them.
Now, I propose to look at some of the patterns from our library. First, we have the simplest one – it is an equality pattern. The idea is that it matches objects that are equal to its wrapped object, in this case integer one. Also, we have and pattern that successfully matches if all of its sub-patterns matches. The same is with or pattern. It matches if at least one of its sub-patterns matches.
The main pattern of our library is list pattern. It consists out of other patterns. In order to match an object, list pattern deconstructs it into smaller parts, and each sub-pattern of list pattern matches each part of the object separately. Then, we have nested list, meaning that list pattern contains another list pattern. And, finally, star list pattern: the principle is the same as in list pattern. The key difference is that list uses one sub-pattern to match one part of deconstructed object, when star pattern may use one sub-pattern to match any number of objects. The sub-pattern marked as star will match as few parts of object as possible to make the whole pattern match successfully.
In addition, we have rewriting pattern. It has two parts: matching, which is a pattern that matches an object, and transforming, which is a block that constructs new object. The block has two arguments. First – it – is the object that we match, second – context – provides additional information and gives access to metavariables, which we can create using the next pattern – named pattern. It wraps another pattern and gives a name (a metavariable) to a successful match for later reference in the transformation side. Plus there is any pattern that matches any object.
In order to perform matching we send #match: message to a pattern with an argument, which is an object that we want to match. In result of such message send, we get either success or failure.
For example, consider code presented on this slide. In the first line, we create two equality patterns and concatenate them into a list pattern, using comma. In line two, we match this pattern with object – a point 2@3.
During the matching in the line two, we deconstruct an object, and match each part of this object with parts of pattern – sub-patterns.
Let us look at another example: here we have pattern that matches AST. First sub-pattern of list pattern in the first line is #var any that matches any object. In the second and third lines, we create AST that represents assignment of integer five to some variable.
During the matching in the line four, we deconstruct an AST same as in previous example, and match each part of this object with sub-pattern. We break down our AST into two parts: variable and value. Each of them successfully matches sub-patterns respectively. Therefore, as you can see we used Phorms to match a point (generic object) and an AST (source code).
Now let us move on to rewriting. In order to match and transform an object we send #rewrite: message to a rewriting pattern with an argument that is an object, which we want to transform. Again, rewriting pattern consists of two parts: matching, which is a pattern that matches an object, and transforming, which is a block that constructs new object.
In our example, the matching part is a list pattern with two sub-patterns that match any two objects. The transforming part is a block that returns a point with reverted coordinates. Here we use it in order to access the coordinates of matched point. In the line five, we rewrite a point 1@2 and get a point 2@1.
To sum up, we introduce new pattern library called Phorms into Pharo, which is capable to match and transform any objects, whether it is built-in Pharo object or user-created. Of course, this applies to ASTs as well, so we can perform source code rewriting. What is even more, patterns are first-class objects. Therefore, we can compose patterns using a number of different predefined combinators and convert them too. The patterns can be inspected and they provide useful information. Matching process can be easily debugged. Of course, Phorms library is extendable, so you are welcome to implement pattern types or combinators.
As for disadvantages of our implementation: firstly, for now there is no specific API / syntax for AST rewriting, therefore in order to perform source code transformation one may have to implement bulky patterns. In the future, though, we plan to implement an extension to our library, which would facilitate AST rewriting.
Thank you for your attention. Maybe there are some questions?