Previous ... Next

Traits and rename

(1/5)An introduction to Programmatic Refactoring

Traits allow us to programmatically rename methods and nested classes to other names. Consider the following example code, defining trait «», containing many nested classes: the interface «», the class «» implementing «», the class «», that is similar to «» but does not implements «» and finally the class «» that uses both «» and «»:

We can extract the code into class «», and then use it to print «». We show below how we can use the operator «» to programmatically rename elements of «» while preserving the semantic:
'A.k()]
Concrete2 = {//equivalent to just writing the following directly:
  A = {interface,  method S k()}
  B = {[A],        method S k()=S"Hi"                    class method This()}
  C = {            method   m()=S" world"                class method This()}
  D = {            method S callBoth()=B().k()++C().m()  class method This()}
  }
Main2 = Debug(Concrete2.D().callBoth()) //still prints "Hi World"
]]>
As you can see above, the mapping «'A.k()]]>» renamed all the occurrences of «» into «». This includes all the declarations refining such method and all the usages of such method, either from the static type «» or from any nested class implementing «». On the other side, you can see how «» was not renamed; the rename is type driven. We can also rename multiple names at once; in this way we can even swap names:
'A.k();
  'B.#apply() =>'B.of();
  'C=>'D;
  'D=>'C;
  ]
Concrete3 = {//equivalent to just writing the following directly:
  A = {interface,  method S k()}
  B = {[A],        method S k()=S"Hi"                       class method This of()}
  C = {            method S callBoth()=B.of().k()++D().m()  class method This()}
  D = {            method   m()=S" world"                   class method This()}
  }
Main3 = Debug(Concrete3.C().callBoth()) //still prints "Hi World"
]]>
Note how the call «» is now replaced with «». «» is the extended name for the method with the empty name. Also binary operator methods can be renamed by using their extended name; The complete list of extended names for binary operators is discussed later, but you can also just use the overview feature to see them in any compiled class. The «» sign is a syntactic sugar similar to the «»; indeed «» is equivalent to «»; where the text after the «» has strict syntactic restrictions, requiring it to be either a valid path (as «» or «», for example), a valid method selector, or a path followed by a method selector. A method selector can also be followed by an argument name, as in «» In 42 programmatic refactoring and other tasks requiring us to express paths and method selectors are very common, and writing «'B]]]>» is so much more convenient that writing «Name"B"]]]>».

(2/5) Programmatic Refactoring: all kinds of operations

Single

Programmatic refactoring of nested classes is transitive by default. All the nested classes are going to be renamed together with the renamed root. The code below shows how to specify a single rename instead:

'K; 'A=>'D.H ]
MultiConcrete = {//equivalent to just writing the following directly:
  D = {
    H = {
      class method S hi() = S"hi"
      B = {class method S world() = S"world"}
      }
    }
  C = {
    D = { class method S space() = S" " }
    }
  K = {
    class method Void print() = Debug(D.H.hi()++C.D.space()++D.H.B.world())
    }
  }
Main4 = MultiConcrete.K.print() //still prints "hi world"
]]>
As you can see, we did a single rename «'K]]>» and a transitive rename «'D.H]]>». Since there were nested classes inside of «», the single rename has left a shell of «» in place so that the nested «» could stay in position.

We can also rename «». For example, with the code below we can make the original top level into a nested class, and the nested class «» into the top level:

'OriginalTop;single='NewTop=>'This]
]]>

On the other side, self rename, for example «'C]]>» is usually an error, and thus it will raise an exception. However, we can silence such errors and turn self rename into a no-op by using «'C]]]>» or «'C]]]>».

Hide

The code below shows how to hide a method or a class:

Private members in 42 are obtained by relying on unique unguessable numbers: These names do not show up in the outline and can not be invoked by user code; moreover those numbers are automatically renamed to avoid clashing during code composition; overall, those numbers are completely invisible for the user of the code. While it is possible to manually use unique numbers, it is so much more convenient to write open code and then seal it later.

Clear

Hidden code is still part of the result, but it is no more accessible. Symmetrically, cleared code is not part of the result, but its entry point is still accessible, but abstract; clearing code allows us to override already defined behaviour, as shown below:

Of course, «» would clear only the nested class «» and not also «».

Soft rename

Clearing code allows us to override code by removing code. This is different with respect to what happens with overriding in most languages, where the former code still exists and can be invoked, for example with «». In 42 we can easily emulate super by using «]]>» instead of «»; the code below shows how «]]>» can be used on both methods and nested classes:

'SuperA; 'C.D.space()->'C.D.superSpace() ]:{
  SuperA = { class method S hi() B={class method S world()}}
  A = {
    class method S hi() = S"[%SuperA.hi()]"
    B = {class method S world() = S"[%SuperA.B.world()]"}
    }
  C={
    D={
      class method S superSpace()
      class method S space()=S"[%this.superSpace()]"
      }
    }
  }
Main7 = MultiConcrete4.C.print() //now prints "[hi][ ][world]"
]]>
Note how in this case we explicitly declare «», «» and «» in the composed code, even if they are already present in the result of «'SuperA; 'C.D.space()->'C.D.superSpace() ]]]>». We will soon show a way to avoid redeclaring them, but our experience programming in 42 suggests that when only a few methods are involved, the code is often more clear and easier to understand by redeclaring them.

Redirect

Finally, programmatic refactoring allows us to rename a nested class into an externally declared class. We call this kind of rename redirect. This also provides a simple encoding for generics. Consider the following code:

Num]
Main = (
  myBox = NumBox(3Num)
  Debug(myBox)
  Num n = myBox.that()
  Debug(n)
  )
]]>
Note how we wrote «Num]]]>» and not «'Num]]]>»: In «Num]]>», «» is the numeric class defined outside. If we instead wrote «'Num]]>», «» would be the class nested inside «» and called «». Generics classes are straightforward to implement with redirect, and indeed «» uses the redirect operator internally.

We can redirect multiple nested classes at the same time, and we can put arbitrary constraints on the structural type of the destination types simply by specifying abstract methods and implemented interfaces. Consider the following example:

e2.myIndex().eval(e1)
    if res return e1
    return e2
    }
  }
Adventurer = Data:{S name, Num attack, Level level}
Level = Data:{
  Num exp
  S profession
  method Num eval(Adventurer that) = {..}
  }

DuelOperation = Class:Operation
  ['Elem.myIndex()=>'Elem.level()]
  ['Elem=>Adventurer;'Index=>Level]

Main= /*..*/ DuelOperation.best(e1=luke e2=gandalf) /*..*/
]]>
Here we can define a generic «» working on «» and «». Elements must have an «» method and indexes must implement «» and offer a «». In a language like Java with F-Bound polymorphism, we would have been required to rely on a «]]>» interface, while in 42 we can simply list the required operations. Note how before specifing the actual types for «» and «» we can tweak the «», so that we can accept the «» method instead of the «» one. Redirect is very powerful; checking also subtype relationships between redirected members, as shown below:
Note how we can also require class methods on the redirect nested classes. Overall, the whole philosophy of generic programming is different in 42: instead of raising the level of abstraction and designing classes with type parameters, we just design normal classes with nested classes, that just so happens to be fully abstract. Those classes will represent external dependencies. Then we can redirect those nested classes onto others.

(3/5) Different ways to supply missing dependencies

As we have seen, in 42 it is convenient to write self contained code, where the dependencies can be specified as nested classes with abstract methods. In 42 there are three different ways to satisfy those dependencies:

  • Sum: We can compose two traits with the operators «» or «» to provide some of the missing method implementations.
  • Redirect: We can rename a class to an external one
Foo]
]]>
  • Rename: We can rename a member to another member in the same unit of code:
'A]
]]>
This last solution works like the sum, but is happening inside of the a single unit of code. If this inner sum is successful, it behaves as trait composition would. There are a few corner cases where this inner sum will fail; they involve details of composing classes with interfaces and adding methods to interfaces.

(4/5) Introspection and Info

It is also possible to programmatically query the code structure and make decisions about it. For example

t2.info().methods().size() return t1
  return t2
  }}
MyClass = Class:Larger(t1=ATrait, t2=AnotherTrait)
]]>
The method «» returns an instance of class «», offering methods to query all the visible information about the trait code. The class «» represents a nested class, and also contains classes representing other kinds of code elements: «», «» and «».
«» contains a lot of useful methods. Some of those methods query information about the class and how it is used in its code unit: for example the method «» returns the list of types whose private members are used.
A class that is watched can not be cleared. Indeed, all the possible errors of programmatic refactoring can be predicted by relying on the methods of «».
«» provides an «» for a library literal and «» provides an «» for a «». For example, while «» can be used to know about this «» method, «» can be used to get information about the «» method.

(5/5)Programmatic refactoring summary

  • Many kinds of operations can be performed on code
  • Rename, as for «'B]]>» or «'A.bar()]]>», is used to rename all the occurrences of a member into another name or form, for the sake of the final user.
  • Soft Rename, as for «'B]]>» or «'A.bar()]]>», only moves the declaration. It leaves in place all the usages and an abstract version of the original signature.
  • Clear, as for «» or «», removes the implementation and all the private details of a member. It leaves in places all the usages and an abstract version of the original signature.
  • Hide, as for «» or «», renames all the occurrences of a member into an uniquely named one. This new name is now completely invisible from outside the code unit.
  • Redirect, as for «Num]]>», redirects all the usages of a nested class into an externally declared one. The internal declaration is simply trashed.
Finally, «» allows us to explore the shape of code as metadata.
      Previous ... Next