понедельник, 25 сентября 2017 г.

Class annotations

In Pharo language we can annotate methods with meta information using special syntax:
MyClass>>someMethod
  <mySpecialPragmaWith: firstArg and: secondArg>
  "method logic"
The meta information is represented by pragma instances. It can be retrieved from methods:
(MyClass>>#someMethod) pragmas
And it can be queried globally from the system:
Pragma allNamed: #mySpecialPragmaWith:and: in: MyClass
You can read details about pragmas here: Pragmas: Literal Messages as Powerful Method Annotations.

Interesting that method pragmas can be used to implement class annotations without introducing special language constructs for them. Many libraries do it without explicit mention of annotations.

Usually the trick is class side method which is annotated with special conventional pragma which arguments are used as class meta information. So you query the system for this pragma and from methods you get classes and from pragma you get metadata.

The problem here is that pragma is quite simple and therefore it is quite restrictive:
  • It is always an instance of class Pragma. So you can not add specific behaviour to it.
  • Pragma arguments are literal objects. So you can not use anything complex in pragmas. And again you can not get metadata with specific behaviour
To address this problems you can return special object from "class annotating" methods. It will allow you to add any behaviour to it and instantiate it together with any other object without restrictions. In that case to query annotations you will evaluate found pragma methods and collect result. In addition you will probably need cache results if annotations are part of some frequent processing logic. And for cache you will need some invalidation logic because "class annotating" methods can be removed or added.

So many libraries implement described mechanizms in one or another way. And they all duplicate implementation of class annotations in one or another way.

For example in Commander users annotate command classes with activators which represent the way how access and execute commands (shortcut, context menu, etc..). Activators are attached as class side methods with pragma #commandActivator. Each method returns activator instance.

Calypso browser has similar mechanism to declare what tools should be in the browser as tabs. Each tool defines class side methods which return the context of browser where it should be shown.

Generally class annotations are very known and useful concept. In Java and C# they are used everywhere: serialization, ORM, web services, whatever.

So I decided introduce class annotations as reusable mechanizm in Pharo. You can look at first version on github https://github.com/dionisiydk/ClassAnnotation. Interesting that it is just one class with two method extensions. Main logic is based on existing PragmaCollector. That's why it is so small library.

So in my implementation every annotation is subclass of ClassAnnotation. To attach it to classes you should create new class side method which will return an instance of the annotation. This method should be marked with pragma #classAnnotation:
MyClass class>>specialAnnotation
  <classAnnotation>
  ^MySpecialAnnotation new
Then you can query annotations using one of the following methods:
You can ask concrete annotation class for all registered instances:
MySpecialAnnotation registeredInstances
And you can ask given class for all attached annotations:
MyClass classAnnotations
All annotations are cached. So it is cheap to query them.

Now I start adopt Calypso and Commander to use this new mechanizm. And I think there are many places where class annotations will simplify the system.

Interesting that it really took time to realize that the things which Commander and Calypso implement are actually annotation logic. Thank's Marcus Denker who helps me with this.