Previous ... Next

More decorators

Using the expressive power of programmatic refactoring many different decorators can be designed. Here we list and explain some of the most useful.

(1/5)Public

The «» decorator allows us to select certain members as «», and to hide all the others: for any nested class containing at least one «» annotation, «» hides all the non «» annotated members.
Consider the following example:

=18I]
    }
  }
]]>
Here we have a «» class with «» and «», but we expose only the «». We also expose the method «», that can update the «».
«» is generating a bunch of other methods, but we are not exposing them. In order to make our «» class usable, we need to at least expose a factory, as we do in line 5.
On the other side, consider «»: since there is no «» annotation in «», no members are hidden. Also, note how the «» invariant can refer to «» normally. This works because the «» decorator is applied outside of both «» and «». This is indeed a very common pattern: «» is often used high up in the nested tree, to allow for tightly connected classes to see each others private members when needed. Using the outline IDE feature, we could see the following:
As you can see, «» exposes all of the methods generated by «», while «» exposes only a minimal interface.

Alternativelly, when we only wish to hide a few specific methods, we could just specify the private members. To this aim, we need to specify in «» the path to use to indicate privateness. We could for example use «», that loosely speaking represents a form of denial:

=18I]
    }
  }
]]>
The code above hides the «» invariant; this of course means that the invariant is still enforced, but methods «» and «» are not polluting the interface of «» any more.

Finally, «» is closing all the nested classes; this means that the fields and constructors are not any more abstract methods but implemented methods delegating to hidden ones. A sealed class encapsulates its state, but can be reused in less flexible ways: the code of two sealed classes can not be merged with trait operators «» and «».

(2/5)Organize

With metaprogramming, often we have to create code in a certain order, and this order may be in contrast with the final structure we want to create. For example, we may want to have an interface whose nested classes implements such interface. However, the following code:

Would not work: we can not apply Data on «» since «» it is not compiled yet. We could use rename and write
'This]
]]>
That is, first we create a bunch of nested classes, and then we organize the result by renaming to obtain the desired shape. This common pattern is automated by the decorator «» performing a standard pattern of renames:
Names containing the special «» character are renamed in names where «» is replaced with a «» or just removed; for example «» is renamed in «», «» is renamed in «» and «» is renamed into «». Thus, we could rewrite the code above as

In metaprogramming systems, code generation needs to proceed in a specific order. This sometimes creates difficoult situations. For example, the following naive implementation of a «» with a map of friends to locations would not work:

«» can not be generated, since «» is untypable until «» is generated.
We can circumvent those limitations with «» by writing:
«» is also very useful to avoid redeclaring abstract methods when extending code. This sometimes requires late typing, usually by introducing an extra nested class that will only be used as a dependency. One such case happened while designing a little 42 videogame; where we encountered the setting below: We have «»s following each other in a «». Each «» knows about the «», the «» values are «» objects. Moreover, we do not just reuse a «» but we add new operations to it: the map is going to have specialized location aware operations.
The former code declares both «» and «», and both «» and «». They will be merged by «» but are still separated inside the code library, before «» can act. This is relevant when type inference is required. For example, method «» can not be simply implemented as «» since there is no method «» in «»; such method is presented in «». The solution is to add an up-cast to «». However, if this code was typed before «» could run, such a cast would not typecheck, since «» is not «». We can easly delay the type checking by adding an annotation: «» casts «» to «» only when also nested class «» can be typed. Since «» is declared after «», this is happening after «» has been applied. Alternatively, we could use a local variable declaration and write the following:

Also method «» requires upcasting; however we do not need to repeat the «» annotation since type dependencies are class-wide: a single «» annotation anywhere in any method covers all the methods of the same class (but not the methods in nested classes). Note that this approach does not rely on any dynamic checks; the 42 upcast operator «» is only guiding the type system, and even if the typing happens later, it will happen before the code is ready for execution. Instead of «» we could rely on the class «» itself, and write «». If we want to make what is happening more explicit, we could even get creative and write «».

(3/5)Data.**

The «» decorator contains many useful nested classes, that can be used as independent decorators.

Data.AddList, Data.AddOpt, Data.AddSet

Decorators «», allows us to add a nested class «» working as a list of «». «» and «» work similarly, but for «» and «». That is, to define a «» supporting both lists of points and sets of points we can simply write:

Note that the order of application of the above decorators is not important.

Data.AddConstructors

«» applies a heuristic to decide what are the field of a class and add two constructors: The first is called «» and the second has the empty name; also known as «». «» simply takes all fields as immutable and produces an immutable result. «» takes the most general type for the fields and produces a «» result if class instances are mutable, and «» otherwise. The most general type for fields may be «» or «». We have not seen «» types yet in this guide; they are useful for circular initialization. For example

This code works in 42 and creates two circularly connected deeply immutable objects. Forward types can also be used as parameters in regular methods, and the type system will check that their values can not be directly accessed but only passed around until they reach an abstract factory method. «» takes two parameters: «» and «». «» choses the nested class to influence, and it is «» by default. «» is false by default, and prevents «» and «» constructors when true. «» can also specify an alternative name for the empty name constructor and the field names and order. «» can also be built as an alphanumeric to simply initialize the «» parameter. Also «» can be built as an alphanumeric and it will internally propagate that parameter to «». The code below provides good examples:
Passing the constructor parameter names explicitly is very useful in case we want to reorganize the order of the fields or explicitly exclude some abstract method that would be inferred to be a field otherwise.

Data.Seal

«» also takes two parameters: «» and «». «» chooses the nested class to influence, and it is «» by default. «» is false by default, and if it is true attempts to use an already existent «» method to only expose normalized values out. It only works if class instances are immutable and fails if any constructor parameter is forward. «» is the part of «» processing and activating of all the «» annotations.

«» implements all of the abstract state operations by delegating to an equivalent private method. The class is then sealed. «» is a convenient class method that applies «» on all the nested classes in a library literal. «» uses «» internally. As discussed for «», code composition of sealed classes is less flexible since the state is now set in stone.

Data.Wither

«» takes an open class and adds «» methods; one for each field. Those methods create a new object calling «», where all the fields are the same, except for the one provided as a parameter. For example, in the usual iconic «» example, we could write «» to get «» Note that this generates only the withers to update a single parameter at a time.

Data.Defaults

In 42, methods are distinguished by their full selector, not their name; this is particularly convenient to encode default arguments, so that calling a method without mentioning a parameter will be equivalent to passing a default value to it. This can be done by hand; as shown in the example below:

The decorator «» allows us to generate those delegator methods more easily. The code above could equivalently be rewritten as follows
Methods starting with «» are recognized by «» and used to create delegators. Moreover, in the same way fields are expanded into methods, the expression associated with the field is expanded in a no-arg «» method. Manually defined «» methods can also take parameters; they must have the same name and type of parameters specified before the current parameter in the original method.
In more detail: for every method where at least one «» method is recognized, another method will be generated.

This generate method will not have any of the parameter with a recognized «»; it will call those «» methods to produce the needed values and delegates to the original method.

Data.Relax

«» works exactly like «», but does not call «» on the result.

Data traits

Finally, the following methods return traits with one operation each, as obvious from their name: «», «», «», «», «», «», «».

Data as a combination of decorators

In the end, «» just composes all of those decorators and traits together as follows:

Where «» applies the trait in the «» position only if this causes no error. In this way if a method with the same name was already defined, the operation is simply skipped.

(4/5)Decorator

The «» decorator simplifies creating new decorators. for example, for a variant of «» that always normalize, we could do as follows, where we simply specify a method from «» into «» that can throw any kind of «».

The «» decorator will then use our code to create a decorator. It will provide the following:
We can also define parameters in our decorator, but we need to ensure the object could be created with the unnamed no-arg factory. For example, to add to «» the option of acting in an arbitrary nested class of the input, we could write:

We can produce very liberal variations of «» by simply re-implementing the method that composes all the individual decorators and traits. For example, if we wanted a variation of data that does not generate the withers, we could just write:

As you can see, with «» we can easy tweak any existing decorator and compose them into new ones. Another interesting example is «»; it is present in AdamsTowel, but it is quite easy to redefine:

«» is a much more challenging decorator to define, but we have finally explored all the needed features. As a reminder, «» generates one enumeration element for any nested class. Thus, for example

would turn the top level nested class into an interface and would enrich those 4 nested classes so that they implement «» and support equality. Moreover, a nested class «» is added allowing us to list all the elements of the enumeration, and to map them from string. We will now see how to encode such a complex behaviour.
'Vals.prev()]  //res.Vals.next is renamed into .prev
      step = TraitEnumStep['E=>nameFromRoot]  //set the current TraitEnumStep name
      res := (step+base)[hide='Vals.prev()]  //the new candidate result composes step and base
      //res.Vals.prev is hidden, so that the next iteration we can rename next onto prev
      )
    res := (res+TraitCacheVals)[hide='Vals.next()]  //res.Vals.next connects the
    //inductive step with the result, and can then be hidden
    (res+trait)[hide='sealed()]  //finally, we compose what we created with
    //any extra code that the user provided, and we seal the top level interface
    )
  }
]]>
The general pattern shown above is quite common when building complex decorators: Start from some base code. Iterate on a number of steps depending on the input trait; for each step combine the base code with code representing this extra step. At the end of each step apply some renaming and hiding so that the resulting code has the same structural shape of the base code. Finally, compose the result with the original user input and some more code providing a better user API.
For «», the base code is as follow:
We have a «» nested, that will be the list type returned by «». In «» we only declare the abstract methods used in «». The inductive code is much more interesting: we declare the top level as an interface, with a «» method. This is the device to finally seal the hierarchy, so that the enumeration only has a fixed set of options. The enumeration offers the three methods that are usually provided by classes supporting equality: «», «» and «». Nested class «» represents an arbitrary element of our enumeration, and provides a standard implementation for those methods. It is a class with sealed state and no fields, thus 42 will implicitly use the normalized value for its single instance. This means that the method «» will simply convert the «» reference to «» without the need of any expensive computation.
«» and «» are now playing the inductive game of growing a list. The base code «» starts with an empty list, and any base case will append to the right the instance of the current «».
Finally, to provide a good and efficient API, we cache «» and «». This is also the place where we provide an actual implementation for «» and «».
We carefully capture and regenerate errors: «» should provide a «».

As you can see, with a little experience it is possible to define decorators that behave like language extensions. Developement on a large 42 program should start defining some appropriate decorators to make the rest of the code more fluent and compact.

(5/5)Metaprogramming summary

  • Metaprogramming is hard; 42 tries to make it simpler, but it is still not trivial.
  • Making your own decorators it is easy when your decorators are just a simple composition of other decorators.
  • Error handling is important while writing decorators. A large part of decorators code should be dedicated to handling errors and lifting them into a more understandable form, for the sake of the final user.
  • We are just scratching the surface of what we can do with metaprogramming. If you are interested in becoming a Magrathean, then join our effort to design the painful metaprogramming guide.
  • In the current state of the art we do not have an answer for what is the best 42 (meta-)programming style. Indeed, we still do not understand the question.

      Previous ... Next