Target audience
This tutorial is design for expert programmers, already knowledgeable in
at least two or three of the languages Java, C#, C++ and Python.
This tutorial lays out the basic knowledge for programming in 42 using AdamsTowel, but
does not explore the foundational theory behind 42,
or the mathematical rationale for the correctness of 42.
The language 42 and many 42 metaphors are inspired by
The Hitchhiker's Guide to the Galaxy by Douglas Adams.
Downloading and running
Currently, you can download and run 42 as a
Java program.
Basics
(1/5)Simple hello world program
Let's look at a simple hello world program:
When we write
«» we are asking 42 to
reuse the code of the library found in the internet address
«».
AdamsTowel is our
towel, that is the set of classes and interfaces that we wish to start from.
A
towel usually plays the role of "the standard library" of most languages.
«» is the main website of 42, where most commonly used libraries are hosted. To reuse code you
need an internet connection; but this also means that you will never have to manually import any code.
Required code will be downloaded and cached on your machine, so you need not to be aware of the existence of this mechanism.
We do not need to always start from AdamsTowel; there are many interesting towels out there, and you may also become skilled in the
advanced technique of towel embroidery.
In this tutorial, all of our examples are expressed reusing «».
At the right of «» we write the expression that
we wish to execute; in this case we just print out using the «» class.
«» is not a method, and «» is not special name either. You can replace it with «» or any other valid
upper-case name. In 42 there is no concept of main method as in
Java or C.
For now you can think of «» as a top level command. We will understand later how this fits with the general language design.
«»
is a simple class whose most important method print a message on the terminal.
In 42, when a class has a most important method, it is conventional to use the empty name, so that can be used with the short syntax «» instead of a more verbose «».
In 42, Strings and numbers need to be created using their type, as in
«» or «».
Indeed «» is just a convenient syntax equivalent to «»; the syntax with quotes is needed to express negative or fractional number literals, as for example «»
or «».
(2/5)Method declaration and call
Let's now define a method and call it.
Here we define a class to host our
«» method.
We write
«» to define a method that can be called on the class object, as in
«».
This is roughly equivalent to a static method in languages like Java or C++ , or class methods in Python.
Note that
«» inserts a value into a string.
Note that the method is called using the parameter name explicitly.
We believe this increases readability.
You may also notice how there are two different usages for curly brackets: if there is at least one «» keyword then the expression is a block of statements,
otherwise the expression is a library literal, which can contains methods and nested libraries.
A nested library
is denoted by an upper-case name, and can be created from a library literal or from an expression producing a library literal.
A library literal can be a class (default case) or an interface (starts with the «» keyword).
A nested library in 42 is similar to a static inner class in Java, or a nested class in C++. It is just a convenient way to separate the various components of our program and organize them into a tree shape.
The class «» from before offers a single class method, has no fields and you can not create instances of «», since no factory is present.
In 42 we do not have constructors. Objects are created by factory methods, that are just normal methods that happen to return an instance of their class. We believe this is a much simpler and more consistent approach to object initialization than having special syntax that encourages programmers to make assumptions about the behaviour of the operations.
(3/5)Simple class with internal state
Now we show a class with state and a factory method:
Here you can see we define a
«» class with coordinates
«» and
«» of type
«»,
unlimited precision rational number.
In addition to
«»,
«»,
«»
and
«»,
«» will offer many other useful methods, since it has been declared using
«».
Indeed, «» is a decorator. Decorators are classes/objects that offer an operator «», called the decorator operator,
whose goal is to translate a library into a better library.
In this case, «» is translating the class «»
into a much longer class, with
a factory method taking in input the fields and initializing them; but also containing
boring but useful definitions for
equality, inequality, conversion to string and many others.
Finally, we define a methods to add to each of the coordinates.
For very short methods we can omit the curly brackets and «».
Indeed, method bodies are just expressions, and the curly brackets turn a block of statements into one expression.
In the method «» we show how to create a new
«» instance and how to call getter methods.
In the method «» we show an improved version, using the «» method, another gift of Data, which allows us to easily create a clone with one or more fields updated.
We can define two methods, «» and «» with the same method name, if parameter names are different.
Note that we always use getters and we never access fields directly.
In many other languages we can use write «» and «». Such syntax does not exists in 42. The same goes for object instantiation; in many languages there is a dedicated «» syntax, while in 42 it is just a method call.
Also, similarly to what happens in Python, we need to use «» to call methods when the receiver is «».
While it makes some code more verbose, naming the receiver avoids ambiguities about scoping and nesting for method resolution.
Decorators
Decorators, such as «», are one of the main concepts used by 42 programmers. We will encounter many decorators in this tutorial.
For now, just get used to the pattern of writing
«» to go from a minimal chunk of code, with method declarations for the application specific bits, to a fully fledged usable class.
The backslash «»
In 42, we can use the «» character as a shortcut.
There are two different ways to use the backslash:
as a keyword or immediately followed by a lowercase identifier.
As a keyword, «» represents the expected type of the surrounding expression.
The slash searches outwards on super expressions until it finds a place with an easily guessable type:
the return type of the method, a method parameter or a local binding with an explicit type.
For example:
could be shortened as
Consider these other examples:
Followed by a method name (and method parameters if any)
a «» represents the receiver of the innermost method invocation.
Thus, for example
could be shortened as
Consider this other example:
In the rest of the tutorial, we will use
«» when it saves space. This shortcut seems unusual at first, but with a little of experience becomes very clear. 42 is a pure OO language, where the method call is the central operation.
This syntax allows for the expressions of the method parameters to depend on the method receiver. We will see that this enables many interesting micropatterns.
(4/5)Collection.list
Lists can be defined using «», as in the example below,
where we define new classes
«»
and
«». Note that those are new classes in a nominal type system, so in
«» and
«» denote different classes, with different types.
As you can see, lists can be initialized with
«».
In this case, this syntax is equivalent to creating a new empty list and then calling the
«» method
one time for each of the expressions separated by
«».
Of course, the parameter type of that method is the element type of the list, so
«» finds it as an easily guessable type.
Consider now the following code:
As you can see, we can use
«» to iterate on multiple collections at once.
In 42 as in most other languages you can have blocks of code where multiple
local bindings are introduced by associating a lowercase name with an initialization expression.
Similarly, the
«» introduces local bindings whose values will range over collection elements by associating them with initialization expressions for iterators.
(5/5)First summary
-
At the start of your program, import a towel using
«», as in «».
-
To define a simple class exposing its state and
some methods working with those, use «», as in
«».
-
You can define methods in classes with the «» keyword.
Use «» for methods that can be called on the class object directly.
-
To introduce the concept of list for a certain type, use
«»
as in the class declaration
«»
Object creation summary
42 supports many different syntactic forms that are convenient for creating objects:
-
12Num: from a numeric representation
-
S"foo": from a string representation
-
Point(x=_,y=_): from the parameter values
-
Points[_;_;_]: from a variable length sequence of values.
Note that in 42 these are all just expressions, and represent one or more methods in the named class.
This means that even concepts quite different from numbers, strings and collections may benefit from this syntactic support.
Digressions / Expansions
Here, after the summaries, we will digress and expand
on topics related to the chapter.
Digressions/expansions may be more technical and challenging, and may refer to any content in any of the other chapters, including the forward ones.
For example, we are now going to make some more precise remarks about:
Method selector
In 42 a method selector is the method name plus the list of all the parameter names, in order.
Methods in a class must be uniquely identified by their method selectors.
This provides a good part of the expressive power of overloading, while avoiding all the complexities of type driven overloading resolution.
Keep control: Modifiers, kinds of references and objects
(1/5)Kinds of objects
In object oriented languages, objects can form complex networks of dependencies by referring to each other using their fields.
The Reachable Object Graph (ROG) of a given object is the set of all objects reachable from it, including itself.
An object is mutated if a field of an object in its ROG is updated .
A mutable object is an object that can be mutated.
The 42 type system is able to ensure that some objects can not be mutated. We call those immutable objects.
All instances of «» are immutable.
The ROG of a «» object contains the «» itself and the «» coordinates.
Fields «» and «» can not be updated and the «» objects are immutable.
Immutable objects are very easy to use but may be inadequate when representing entities whose state can change across time.
Let's now define a mutable «», whose location can be updated:
There are two new keywords used here:
-
The «» field is «».
This is called a variable field: a field that can be updated by calling a setter.
Non-variable fields can not be updated.
-
«» is a «».
We have seen a «» already, and we have seen methods
such as «» and «»
showing no modifier;
they implicitly have the default modifier «».
Similarly, whenever a type does not specify a modifier,
it has the default modifier «».
«» methods can mutate the «» object. If you have experience with C++
you can see the contrast with const methods.
Immutable (default) methods works only on immutable «» objects.
Later, we will see much more about modifiers.
As you see, we are using the «» method from before.
Also notice that we are calling the setter «» without providing the parameter name.
While this is usual in other languages, in 42 parameters are selected by name.
Sometimes writing down all the parameter names can get tedious.
If the first parameter is called «», we can omit it:
Writing «» is equivalent to writing «».
This works also for methods with multiple parameters, if the first one is called «».
Writing «» is equivalent to writing «».
We can use «» by writing, for example:
(2/5)Interaction between mutable and immutable
We now explore some interaction between mutable and immutable objects.
Here we use
«» to denote a mutable list of points.
Note the absence of
«»; this is conceptually similar to a
«» in C++ or
«» in Java.
To contrast, the declaration
«» is similar to
«» in C++ or
«» in Java, for an opportune
«» class.
«» references always refer to mutable objects.
«» references always refer to immutable objects.
Fields can be declared
«» independently from their modifier:
In the code above, you can see that
«» is a
«» field of
«» type.
On the other hand,
«»
is a non-
«» field of
«» type.
The method «»
first uses the «» setter method to update the «» field
with the «» leftmost element of the field «».
By the way, collections in «» are primarily designed to store and retrieve
immutable objects; later we will show also how to manipulate mutable ones.
The method then uses the «»
exposer method and
the «» method to mutate the list of points.
Both exposers and getters provide access to the value of a field;
exposers are used to access the values of mutable fields.
Exposers should be used with care:
long term handling of references
to (parts of) a mutable object could cause
spooky action at a distance.
In general, methods starting with # should be used with care.
This code models an animal following a path. It can be used like this:
In this code the first dog goes to 12: 20.
The second dog goes to 0: 0.
This code involves a mutable animal with a mutable field. This is often
a terrible idea, since its behaviour may depend on aliasing: what happens if two dogs follow the same path?
The first dog moves and consumes the path for the second one as well.
That is, the first goes to 12: 20 and the second goes to 1: 2.
This is because
«» is
deeply mutable : a mutable object with mutable fields.
An amazing amount of bugs is caused by deep mutability.
Note that we are using the exposer method
«»
in a safe pattern: it is only called over
«», and the returned reference does not leak out of the method.
The problem here arises since the object was shared to begin with.
(3/5)Capsules: Keep aliasing graphs untangled
In 42 we can change «» to prevent this aliasing issue.
Now we use the modifier
«»; this requires the value of the field to be encapsulated.
Immutable objects are also encapsulated since
they do not influence aliasing, so they are free from aliasing limitations.
The
«»
modifier
forces the users to provide
«» values,
and
ensures
that instances of
«» have
encapsulated state;
that is, the values of all fields in an
«» are encapsulated.
A mutable object with encapsulated state can only be mutated by calling one of its methods.
This allows for the same kind of local reasoning as if all of the fields were immutable.
A capsule mutator is a class method whose first parameter is the capsule field as «».
It is a way to mutate the value of a capsule field without exposing it.
«» recognizes only the methods annotated with «» as capsule mutators.
Those methods can then be safely accessed as instance methods with the same name.
The annotation is called «» because
capsule mutators also clear all the object based caches. Automatic caching is one of the coolest features of 42 and we will explore it later in this tutorial.
Note that we cannot have «» exposers («») for capsule fields:
other code could keep those references and then
mutate the ROG of the field,
breaking local reasoning about Animals.
With «», we are forced to initialize two animals using different paths:
where the
«» local binding is
«»;
it can satisfy the Animal.path requirement, but it can be used only once.
«»
has to use another capsule. It is okay to just write the object creation in place as is done.
Alternatively, lists offer a
«» method,
so in this case we could write
«»
(4/5)Handle mutability
Immutable objects of any class
How can we get an immutable «»?
When an «» is created using «» we create a «».
In most cases you can promote such reference to immutable/capsule; just make the type of the local binding explicit.
The type system will take care of the rest.
If a reference can not be safely promoted to immutable/capsule, you may have to clone some data or to refactor your code.
immutable
dog1.move()
//dog2.move() //ill-typed, requires a mut Animal
]]>
We will not explain in this tutorial the exact rules for promotion, but the main idea is that if the initialization expression uses local bindings in a controlled/safe way, then promotion can be applied.
For example, a mutable expression using only capsule or immutable references can be promoted to capsule or immutable, as we prefer.
lent and read
We have seen immutable, mutable, capsule and class.
The are still two modifiers: «» and «».
They are hygienic references: they can be read but can not be stored in mutable/capsule/immutable fields.
«» is an hygienic mutable reference, allowing mutation but not long term storage.
«» is an hygienic read-only reference.
A method with a single mut parameter can still be called using a lent reference in place of it.
«» is the common supertype of «»,«», «» and «».
In general, we can
use «» when we do not care about the mutability of an object.
For example, we could add to «»
This method can be called on both mutable and immutable animals:
(5/5) Summary
Kinds of classes, summary
-
immutable classes: have only immutable fields.
It is useful to model mathematical concepts.
It is easy to reason about code using immutable classes,
but some properties of real objects can be better modelled with state mutation.
-
shallow mutable classes: have only (variable) fields of immutable or capsule type (or class, as we will see later).
Reasoning with shallow mutable classes is near as easy as reasoning with immutable ones, and often more natural.
-
deep mutable classes: have mutable fields.
Reasoning with deep mutable classes can be very hard.
Modifiers: summary
-
immutable: the default. When you omit the modifier,
you mean immutable.
An immutable reference points to an object that is never changing. Its whole reachable object graph never changes and is immutable as well.
-
mutable: A mutable reference behaves like a normal reference in Java, C#, C++ , Python and many other languages.
Mutable references require mutable objects and allow mutating the referred object.
-
capsule: capsule references are used only once and they guarantee that the whole reachable object graph is reachable only through that
capsule reference.
Capsule references provide a structured way to reason over deep mutable objects.
Fields can be annotated capsule, the meaning is that they need to be initialized/updated with capsule variables.
We will discuss more about capsule fields and how they differ from capsule references later.
-
read: A readable reference can not be used to mutate the referred object; but other mutable references pointing to the same object can mutate it.
Read references can point to both mutable and immutable objects.
It is easy to be confused between read and immutable references.
As a rule of thumb, if you are in doubt about whether to use an immutable or a readable reference,
you probably want an immutable reference.
-
lent: a hygienic mutable reference allowing mutation but not storage.
Lent and read are useful to handle in controlled way the state of deep mutable classes;
moreover using lent and read on method parameters
allows to make explicit what are the method intentions and requirements.
-
class: class references denote the class object,
on methods the meaning is the same of static methods in many languages, but it can consistently be used on parameters/local variables/fields
to encode behaviours similar to dependency injection.
Keep control, summary
-
mutable: mutable objects can be freely aliased and mutated. They allow for a liberal programming style like we can find in Java/C++/C# or Python.
They can be referred to by capsule, mutable, lent and read references.
-
immutable: immutable objects
can be obtained by promoting instances of mutable classes.
They
can be referred to only by immutable and read references.
-
class: class objects can be accessed from anywhere by using the corresponding class name;
It is also possible to
store them into (class) local binding.
Some programmers found the fact that class objects are instances of themselves deeply concerning
or disturbing, while for others it is just a good story to tell to break the ice at parties.
Basic classes
(1/5) Num
«» is a general number type,
implemented as an arbitrary precision rational.
When in doubt of what numeric type to use, «»
is a good first guess.
Some examples of usage:
Another useful numeric type is
«», for index and index offsets.
It corresponds to sizes and indexes in sequences.
«»s are returned by
«» methods
and are expected as parameter by indexing methods.
«» represent 32 bit integers with the usual
but tricky modulo arithmetic.
Other numeric types
AdamsTowel offers two other numeric types:
«» (64 bits floating point) and «» (64 bits integers, rarely used).
Conversions
Conversions between various numeric classes must be performed explicitly.
AdamsTowel offers a simple way to convert between numeric classes; all numeric classes implements «»
so that they can be converted in to each other using the empty named method. For example we can convert indexes into doubles by writing «».
This will avoid precision loss as much as possible.
(2/5) Units: An example library
We will now see how to load and use an interesting 42 Library:«».
Consider the following code, where the class decorator «» allows us to load libraries and embed them in the
current context, while the
«» keyword imports the code from the web.
The library
«»
offers methods to create units out of numeric supports, like
«» and
«».
The code above shows how to create a
«» unit and use it to represent a person age.
Units can be added to themselves and multiplied by constants; for example
«» and
«» would hold, but
«» would not compile.
Units could be used to manually define
all of the units of the SI system.
Prebuilt reusable code for the SI system is already provided in the library; we simply need to specify the desired support, as shown in the code below:
Num]
..
res = (6SI.Meter + 4SI.Meter) * 2Num //20Meter
//wrong = 6SI.Meter + 2SI.Second
]]>
«» is a
trait; traits contains reusable code and operations to adapt it to the current needs.
We will see more on traits (much) later in this guide.
In the case of
«», we can adapt it to many kinds of numeric support and extract the code using
«Num]]]>».
The syntax
«Num]]]>» maps the class called
«» inside of the library onto the class
«» defined outside of the library. We will explain the precise use of such mappings later.
As you can see, we can sum meters together, and we can use the support for multiplication, but we can not mix different units of measure.
Mathematically you can obtain the support out of the unit by
division; that is, 42 meters divided by 2 meters is 21.
Units also provide method «»,
which is just extracting the value of the support from the unit.
This can be convenient during programming but
does not make a lot of sense mathematically and thus
it should be used with care, similarly to other methods starting with «».
Some code which uses units in interesting ways:
0SI.Newton (Debug(S"I can fly"))
myAcc = myLift/stackWeight
reachedHeight = (myAcc*t*t)/2Num //after t (10 sec)
//works assuming the rocket fuel burnt in 10 sec is negligible
]]>
(3/5) Alphanumeric
In the same way «» allows easy creation of
arithmetic classes,
«» allows easy creation of alphanumeric classes:
classes that can be instantiated from a string literal that follow certain
properties.
Note: we raise an error if
«» does not have the shape we expected.
We will see errors/exception in more detail soon.
We can define fields, and compute their values by parsing the string.
It is common to propagate the original string from the factory into the object; but
it is not mandatory. For example you could apply some form of normalization, as shown below:
(4/5) Enumerations
Enumerations can be obtained with «», as in the following code:
Enumerations allows us to add operations as methods on the cases, as shown below:
(5/5) Summary
-
We had a look at
the most basic features of AdamsTowel.
There is rich support for defining your own specialized data structures instead of having to rely on the ones provided by default.
-
Use «», «» and «» to give meaning to your constants.
In this way, the type system will help you to use values with the semantics that you decided.
Be sure to define all of the right base classes to establish a convenient vocabulary
to talk about your problem domain.
Errors and Exceptions: Messages in AdamsTowel
(1/5)Errors, Messages, Asserts, Guards, .... So much terminology
In 42, when something takes an unexpected turn,
you can throw an «».
This is similar to Java unchecked exceptions.
Every immutable object can be thrown as an error.
While it is possible to thrown informative strings, they do no offer enough
structure to fully take advantage of the error mechanism.
AdamsTowel defines the interface «»:
a structured way to provide a certain kind of message to the user.
There are two main kinds of «»:
«» and «».
While assertions are useful to observe bugs, the application
logic should not depend on them, since they may change
in unpredictable ways during library evolutions, and can be enabled or disabled.
Since program logic can depend on guards being thrown,
guards need to be consistent across library evolution.
Assertions are a convenient tool to prevent the code from proceeding
out of our designed state space. The assertion class called «»
looks like a road sign
and
represents a feeling of "NO/PROHIBITED/FORBIDDEN" or something similar.
Assertions are also very convenient for checking pre/post conditions.
The following code show usages of «» (for preconditions and, in general, blaming the client of a function)
and «» (for postcondition checks in the middle and, in general, blaming the function implementation).
0Num; //simplest form
answer<10000Num msg=S"here with personalized message answer= %answer";
actual=answer, expected=42Num //do a better error reporting
] //in a bunch of assertions, they are all going to be checked/reported together.
recomputedAnswer = 6Num*7Num
X[//postconditions/checks in the middle
actual=recomputedAnswer
expected=42Num
]
X[answer==recomputedAnswer]
if answer>50Num (//how to just throw error X
error X""
)
]]>
As you have seen, we have various ways to check for condition:
«0Num;]]>» checks a boolean condition,
«» checks the condition and uses a custom error message,
«» takes two immutable values
and checks that they are structurally equivalent.
«» is often used as last case in a sequence of if-return;
for example, instead of defining «» inside of «», we could compute it externally as shown below:
As you can see, since there are only 4 directions, we believe by exclusion that the last case must hold. However, we prefer to
make our assumptions clear and have them checked.
(2/5) Create, throw and capture
Create and throw
You can create new kinds of messages using «»
as a decorator:
In 42 interfaces can not have implemented methods, not even class ones, so you may be surprised that we can use
«» as a decorator, since decorating is a method call.
When operators are called on a class name directly, they are desugared as a method on one of its
nested libraries. For example
«» becomes
«».
It is very common for an interface to be usable as a decorator, creating new code with a meaningful default implementation for the interface.
Capturing errors and exceptions
In 42 there is no explicit «» statement,
but any block of code delimited by round or curly brackets can contain «».
In the code example below, lines 2 and 3 are conceptually inside the implicit «» statement.
If nothing is thrown then lines 6, 7 and 8 are executed.
Note that «» and «» can see «» and «»; this would not naturally happen in a language with explicit «» statements; «» and «» would become local bindings inside the «» statement.
The catches above do not see local variables
«» and
«» because they may be capturing an error raised by the execution of the initialization of such variable.
L42 never exposes uninitialized data.
If a catch is successful, then the result of its catch expression
will be the result of the whole code block.
In this way, blocks with catches behave like conditionals.
That is, the code above can assign to
«» either
«»,
«» or
«».
Strong error safety
In 42, error handling guarantees a property called strong error safety
(strong exception safety in the Java/C++ terminology).
This means that the body of a catch must not be able to observe
state mutated by the computation that threw the error.
This is enforced by disallowing catching errors in some situations.
That is, the following code do not compile
While the following is accepted.
As you can see, in the first version of the code,
«» is declared outside of the block
and
«» mutates it.
«» would be visible after the
«» is completed.
In the second version instead,
«» is out of scope after the
«» is completed, and the whole mutable ROG reachable from
«» is ready to be garbage collected.
Intuitively, a programmer who does a bunch of sequential operations on some mutable objects
would expect them all to be executed.
They expect the intermediate states of those objects not to be relevant to the surrounding program. Consider the following example:
Reading this code, most programmers would expect this method to keep the 3 counters aligned.
Exceptions and errors violate this intuition, since they can be raised in the middle of the sequence and prevent the later operations.
For example, if «» fails, then Bob will miss his party, possibly because he is too drunk.
This violates the programmers' expectations
outlined above.
Exceptions can be accounted for, since the type system knows about them; so the programmer can be expected to plan for them.
On the other hand, errors can be raised
anywhere and human programmers often
account for them only as a last resort.
Thanks to strong error safety, this natural attitude of human programmers is somewhat mitigated: while it is true that Bob will miss his party, the program will never observe him in this sorry state. Bob is, indeed, ready to be garbage collected.
Without strong error safety, we could simply catch the error and keep observing Bob in his distress.
(3/5) Exceptions and errors
Exceptions are like checked exceptions in Java.
As with errors, every immutable object can be thrown as an exception.
You can just write «» instead of «» while throwing or catching. When catching, «» is the default, so you can write «»
instead of «».
Exceptions represent expected, documented and reliable behaviour;
they are just another way to express control flow.
They are useful to characterize multiple outcomes of an operation,
where it is important to prevent the programmer from forgetting about
the many possible outcomes while focusing only on their preferred one.
Exceptions are checked, so methods leaking exceptions have to
mention it in their headers, as in the following.
The programmer using
«» has to handle
the possibility that the cancel button was pressed.
However, L42 supports exception inference; to simply propagate the exceptions leaked out of the methods called in a method body, you can write
«», as shown below:
Exceptions do not enforce strong exception safety as errors do,
so they can be used more flexibly, and since they are documented in
the types, we can take their existence in account while writing programs.
Often, the programmer wants to just turn exceptions into errors.
While this can be done manually, L42 offers a convenient syntax: «».
The two snippets of code behave nearly identically:
in the second, the thrown objects are also notified of the
position in the code where they are whoopsed.
This is conceptually similar to the very common
Java patten where checked exceptions are wrapped in unchecked ones.
As we have shown before, we can use
«» to mark branches of code
that the programmer believes will never be executed.
«» implements
«», so code capturing
«» is unreliable: as explained before, AdamsTowel programmers are free
to change when and how assertion violations are detected.
In particular, the programmer may recognize that
such a branch could be actually executed, and thus replace the error with correct behaviour.
Assertions should not be thrown as exceptions, but only as errors.
(4/5) Return
As we have seen, we have used «» to exit
from the closest surrounding pair of curly brackets.
Also curly brackets can have
«» or «», which must complete by throwing a
«»,
«» or
«».
Let's see some examples:
Moreover, curly brackets can be used
to
«» a different result if some computation fails:
Return looks similar to error/exception
Return is actually another thing that can be thrown and captured.
While only immutable values can be thrown as errors/exceptions,
return can throw any kind of value, but returns can not leak
outside of the scope of a method.
Hold your head before it explodes, but curly brackets are just a syntactic sugar
to capture returns; these two snippets of code are equivalent:
Depending on how your brain works,
knowing the mechanics of «»
can help you to use return better and understand why you can omit
«» for simple method bodies, and why you can
write multiple groups of curly brackets and have local returns.
Or it may just be very confusing. If you are in the second group, just
never ever write «» explicitly and continue
on with your 42 experience.
(5/5) Errors, exceptions and return, summary
-
Always detect misbehaviour in your code, and
terminate it with an «».
-
Whenever something outside your
control happens, give it a name and throw it as an error, as in:
It just takes 2 lines, and will make debugging your code so much
easier.
-
Use errors intensively, but use exceptions sparingly:
they are needed only in a few
cases, mostly when designing public libraries.
-
To convert exception into errors, use the convenient short
syntax «».
-
Instead of manually writing long lists of leaked exceptions,
you can use «». This is particularly convenient for small auxiliary methods.
-
It is sometimes possible to write elegant and correct code
that is not covered in layers upon layers of error/exception checking,
but often is not possible or not convenient.
Up to half of good 42 code will be composed of
just error/exception handling, repackaging and lifting.
Do not be scared of turning your code into it's own policemen.
Caching
(1/5) Normalization
One of the big advantages of deeply immutable objects is that two structurally identical objects are referentially transparent, that is, you can not distinguish whether they are represented in memory by one object or two.
This means that it is possible to reuse the same objects to save memory. While in other languages the programmer would have to implement some specific support to reuse objects, in L42 this is supported directly in the language, by a process called normalization .
An immutable object can be turned into its normalized version using «».
As you can see, objects are born not normalized and can be normalized manually by the programmer.
Normalization is cheap but it is not free, so it is not convenient to apply it on short lived objects.
The programmer can express that all objects of a class are normalized during creation by passing parameters to
«»:
Normalization starts by checking if another structurally equivalent object has ever been normalized.
Normalization normalizes all the sub objects
and also supports circular objects.
Consider the following richer example:
Normalizing an object normalizes the whole ROG.
In the example, normalizing the two dogs also normalizes their owners, to the same normalized object.
All of the dogs' fields are then replaced with the normalized versions, so the two dogs now share an owner object.
Note that bob1 and bob2 are still two different objects.
The logic needed for normalization is the same needed to check if two arbitrary objects are structurally equal, to print an object to a readable string and to clone objects.
Thus data allows for all of those operations indirectly relying on normalization.
Those are all operations requiring to scan the whole ROG of the object, so the cost of normalization is acceptable in context.
(2/5) Lazy Caching
Some methods may take a long time to compute, but they are deterministic, and thus we could cache the result and reuse it many times.
A typical example is Fibonacci:
This Fibonacci implementation would take a very long time to run, since it would require recomputing the same
results an exponential amount of times.
This tweaked implementation relying on caching is much faster.
As you can see, instead of a method with parameters we can declare a class with fields and an empty named method doing the actual computation.
«» is an annotation recognized by
«» that works only on
«» or
«» methods with no arguments and with an
«» result.
That is, 42 does not directly recognize the annotation
«».
Decorating the surrounding library with
«» translates
«» into
an actual implementation.
«» is a
computation object : an imm object whose only goal is to support one (or more) computationally intense methods.
Thanks to normalization, the cache of computation objects is centrally stored, and thus recursive calls computing Fibonacci will be able to reuse the cache from other objects.
That is, the method result is cached on the normalized version of the receiver. In this way,
all the redundant Fibonacci calls are avoided.
As you can see, the caching is is completely handled by the language and is not connected with the specific algorithm. This pattern is general enough to support any method from immutable data to an immutable result.
(3/5) Automatic parallelism
When decorated by «», «» caches the results of methods after they have been called the first time.
However, why wait for the methods to be called?
Once the receiver object is created, the method could be computed eagerly in a separate worker, so that when we call the method, we may get the result without waiting at all.
That is, if we use «» we can get automatic parallelism: the language will handle a set of parallel workers to execute such method bodies.
An important consideration here is that both «» and «» are unobservable; that is, the observed result of the code is not impacted by lazy or eager cache.
Consider the following code:
Here we declare a Task object with a string field and two eager methods: one will check if the text in the string is polite and another will check if the string is grammatically correct.
This can take quite a while. By using eager methods, it is sufficient to just create the objects to start those computations in parallel.
When we need the results, we can just iterate on those task objects and call the methods.
Objects with eager methods are automatically normed during creation, as if we used
«» instead of
«».
As you can see, in 42, parallelism and caching are just two sides of the same coin.
(4/5) Invariants and derived fields
We have seen that cached behaviour can be computed lazily or eagerly on immutable objects.
But we can bring caching even earlier and compute some behaviour at the same time
as object instantiation.
This allows us to encode derived fields:
fields whose values are completely determined
by other values in the same object.
Consider the following example:
where the class
«» has 3 fields, but the value of the third one should depend only on the other two.
In 42, the code above would simply define a class with three unrelated fields, and while we are offering a factory that conveniently takes
«» and
«» and initialize the third field with
the computed value, the user could easy create invalid instances by calling the factory method with three arguments.
As we will see later, in 42 we can prevent this from happening by making such a method private.
However, we would still be able to create an invalid
«» inside of other
«» methods.
Ideally, we would like to truly have only two fields, and have the third one as a precomputed derived value.
In 42, we can encode such concept using
«»:
The
«» class defined above has a single factory method taking just
«» and
«». In this way there is no need to have multiple ways to build the object and then hide the dangerous ones after the fact.
The method
«» is computed when a
«» object is created.
Moreover,
«» adds a method
«», allowing us to read the computed/cached value as we would if it were a field.
Note that
«» makes a
«» method calling the
«» one.
If the method
«» leaks an error, it will be propagated out as if the method were manually called during object construction.
This means that any time you receive a
«», it has a valid distance.
We can build on this behaviour to encode class invariants:
«» methods with «» return type
designed to simply throw error if the state is incorrect.
For example, consider this updated version of «»:
=0Double && y>=0Double) error X"""%
| Invalid state:
| x = %x
| y = %y
"""
}
]]>
Now, every time user code receives a
«», they can rely on the fact that
«» and
«» are
non-negative and not NaN.
(5/5) Summary
In 42 immutable objects, can be normalized in order to save memory.
This works also on circular object graphs. In case you are interested in the details, it relies on a variation of DFA normalization.
As a special case, objects without fields (immutable or not) are always represented in memory as a single object.
Results of «» and «»
are attached to the normalized version of an object, thus making it possible to recover them simply by building a structurally identical object.
There are three kinds of caching, depending on the time the caching behaviour activates:
-
«» computes the cached value when the annotated method is first called.
It works on «» and «» no-arg methods.
An obvious workaround for the no-arg limitation is to define computation objects; this also works well with normalization: computation objects will
retrieve the cached results of any structurally equivalent object.
-
«» computes the cached value in a separate parallel worker, starting when the object is created. It only works on «» no-arg methods of classes whose objects are all deeply immutable.
Those classes will automatically normalize their instances upon creation.
-
«» computes the cached value during object construction.
Since the object does not exist yet, the annotation can only be placed on a «» method whose parameters represent the needed object fields.
This annotation does influence the observable behaviour.
If there is no error computing the «» methods,
then the fully initialized object is returned.
But, if an error is raised computing the cache,
instead of returning the broken object, the error is leaked during object construction.
This, in turn, allows us to encode class invariants and to provide a static guarantee that users of a class can rely upon.
Caching on Mutable objects
(1/5) Cache invalidation
The main advantage of caching methods of immutable objects is that the cache stays valid.
L42 can also cache methods of mutable objects, and discovers on its own when the cache needs to be invalidated.
Consider this trivial variation of the «» example above where the fields can be updated:
When a setter for
«» or
«» is invoked, then the two
«» methods are recomputed.
In other programming languages, this behaviour can be encoded by
making the fields private and customizing the implementations of the setters to recompute the distance when needed. This pattern can grow very complex very fast.
L42 guarantees that a cached value is always structurally equivalent to the value that would be returned by calling the method again.
Moreover, for
«», L42 also guarantees that if the computation was re-run then it would terminate without errors.
Thus, when
«» is used to emulate invariants, those invariants are guaranteed to hold for all observable objects, that is, all objects where the annotated method could possibly be called.
This is possible thanks to the strong L42 type system, and we believe this property can not be broken.
That is, we believe this property to hold even in the presence of exceptions, errors, aliasing, input output and non deterministic behaviour.
It is possible to make L42 work together with Java or even with (possibly broken) native code, and we believe our property will continue to hold.
(2/5) Deeply mutable objects
As discussed above, a deeply mutable object is a mutable object with some mutable fields.
Also deeply mutable objects can support «»,
but such objects must have encapsulated state, as we have seen before for the class
«».
The field
«» is an encapsulated mutable field.
It can be accessed as
«» by doing
«», but can not be directly accessed as
«».
However, we can write
capsule mutator methods by using
«».
Similarly to
«», a class method can be annotated with
«» and can
take parameters representing the object's fields.
In addition, more parameters can be present encoding extra arguments.
To clarify, consider this richer example, where our
«» has an invariant and another capsule mutator method:
maxD (
maxI:=i
maxD:=currentD
)
)
if maxI!=I"-1" path.remove(maxI)
)
@Cache.Now class method
Void invariant(read Points path, Point location) =
if path.contains(location) error X"..."
}
]]>
We added a method to remove the farthest away point if it is over a certain distance.
As you can see, the parameters
«» and
«» corresponds to fields, while
the parameter
«» is extra needed information.
When we call
«» we pass only
«»; the other parameters are passed automatically.
As an invariant, we require that the current location is not in the
«».
This code, in the current form, has a bug; can you spot it?
Look carefully at the method
«»:
Here we first set up the location, then we remove it from the path.
The method
«» recomputes the invariant twice: one after the field setter
and one after the call to the
«» method.
This first check is going to fail, since the leftmost element of the path has not been removed yet.
In this case we can solve the problem by swapping the lines:
However, this kind of solution does not scale in the general case.
Next, we will see a
programming pattern that allows
the invariant checks
(and more generally
the recomputation of
«» methods)
to be delayed in a controlled way.
Cache.Lazy and Cache.LazyRead
As we have seen before,
we can annotate «» and «» methods
with «» so that
the result will be computed once, the first time that
the method is called.
We can also annotate «» methods in the same way.
However, the cache is now stored in the actual objects and not in the normalized versions.
This happens because a «» reference can refer to either mutable or immutable objects, and only immutable objects
can have normalized versions.
If anything in the ROG of the «» object is mutated, then the cache is invalidated,
and the result will be recomputed the next time the method is called.
Indeed this annotation enables lazy caching on mutable data-structures, where the cache is automatically invalidated and removed when a «» method terminates.
Finally, since the type system can not track when the ROG from «» fields is mutated, a «» can only be applied to
classes whose fields are all
«», «» or «»;
that is, their instance all have encapsulated state.
If a class has «» fields, but those are not actually used to compute the cached value,
we can apply the «» annotation to an opportune «» method instead.
Every method annotated as «» could instead be annotated as
«».
This annotation is a point in the middle between «» and «»; it produces the same behaviour as «» but works similarly to «»: it is applied to
«» methods whose parameters represent fields, and «» generates a correspondent no-arg «» method.
«», «» and «» methods all behave as if they where recomputing the result, but with a different performance.
Cache invalidation is considered one of the great challenges in writing correct programs; L42 can handle it correctly and automatically.
However, there is a cost: you have to encode the algorithm so that the type system accepts your code and so that the caching annotations can be applied.
(3/5) Box patten
As we have seen, in order to
write mutable objects with encapsulated state,
we need to design them well, using «» to initialize the mutable data, using «» to mutate such state, and «» for the invariant.
However, we can also program a naive deeply mutable object and box it up as a second step.
This can require a little more code, but it is more intuitive, and works very well for arbitrarily complex cases.
Consider the following code:
As you can see, the
«» is a deeply mutable class, designed with no attention to
correctness: if the programmer is not careful, the same
«»
may end up being used for multiple bikes at the same time.
Also, the method called
«» only represents a programmer intention, but it is not enforced in any way, so it could be silently broken.
We can easy create a
«» class containing and encapsulating, such a
«»:
As you can see, no matter how complex some class code is, we can simply wrap it into a box and apply
«»
and
«» on top of it.
In this way we end up with two types:
«», that does not offers any guarantee,
and
«», ensuring the invariant and
encapsulating the state.
The methods
«»,
«»
and
«»
will check for the invariant exactly one time, at the end of their execution.
Following this pattern, we can perform an arbitrarily long computation before the checks are triggered.
When writing other classes, we can choose to use
«»
or
«», depending on the specific details of our code.
If we chose to use
«» as a field of another class, we can still check
the
«» invariant inside the invariant of the composite class:
As we have seen, with the box pattern we
can have the flexibility of temporarily open invariants without any of the drawbacks.
Of course, programmers will need to keep in mind which values are protected by invariants
and which values are unsupervised by invariants.
In 42 this is under the control of the type system: a value of type «» has no special guarantees, while a value of type
«» will ensure the invariant.
(4/5) Controlling the ROG shape
An attentive reader may have notice that we would allow for fields «»
and «» to point to the
same «» object.
A Java programmer may be tempted to just add
«» in the invariant,
but this would just use the user defined «» operator, that
on classes created using «» is likely to check for structural equality instead of pointer equality.
AdamsTowel offers «» to check for reference equality, but
this method only works for «» objects.
The wheels are indeed «» objects,
but the invariant method takes a «» receiver; thus we can only see the wheels as «».
In this case, the inability to use pointer equality is actually a good thing, since it does not correspond to what we really wanted to express: what if the two wheels are different objects but they share the same «» object?
What we want is to check that the mutable objects are not aliased in physically unreasonable ways.
Generally, what we often want is to ensure the tree shape of the mutable part of the object graph.
In 42, we can create classes where all of the instances are guaranteed to follow this property, by making all fields either «» types of classes that recursively respect this property
or «»/«».
However, according to what we have seen up to now, «» fields can only be mutated by defining «» methods, and those methods will be unable to mutate any other
«» field.
Consider the following code:
Here the
«» can rub onto the wheel, damaging it.
The parameter of method
«» is
«». This guarantees that the object graphs of the chain and the wheel will
not be mangled together by the method
«».
Can we assemble a
«» while being able to both use
«» and guaranteeing the tree structure?
The trick is to declare
«» exposers manually:
That is, by declaring lent exposers manually we gain the possibility of writing methods that mutate the capsule fields without using the
«» pattern.
In exchange for this extra flexibility,
those fields do not count as
«» fields for the sake of
«»,
«» or
«» annotations.
However, we can still use through the box pattern, as show before.
(5/5) Summary
Representation invariants and lazy caching
can be applied to mutable objects as well as immutable ones.
Proving properties on mutable
objects requires us to know and apply various patterns.
Historically, in software verification, representation invariants where small
units of code, mostly focusing on the direct content of the fields and mostly relying on either pointer equality or the binary value of primitive datatypes, where the invariants could be deliberately broken while the program was still able to observe the broken object.
None of this is possible in 42; in particular, 42 guarantees that no broken objects can be observed.
The box pattern allows us to divide the value into two types:
the one with an enforced invariant and the raw object state.
This recovers
the flexibility of temporarily open invariants
without any of the drawbacks.
Note that a sound language with normalization and caching/invariants
can not offer pointer equality tests on immutable objects.
Consider the example of a list whose invariant requires all
of its elements to have a distinct pointer values.
A list with such an invariant may contain two structurally equal but not pointer equal elements.
Suppose such a list became immutable and was
normalized.
Now those two elements
would be pointer equal, and the invariant would have been broken by normalization.
It can be hard to remember the differences between all of the «» annotations.
Below, we show a table summarizing them.
annotation |
recType |
parameters |
transformedInto |
storage |
timing |
Cache.Lazy |
class |
zero |
|
class |
first call |
Cache.Lazy |
imm |
zero |
|
norm |
first call |
Cache.Lazy |
read* |
zero |
|
instance |
invalidation |
Cache.Eager |
imm* |
zero |
|
norm |
parallel |
Cache.LazyRead |
class |
fields |
read0 |
instance |
invalidation |
Cache.Now |
class |
fields |
read0 |
instance |
invalidation+ |
Cache.Clear |
class |
fields+ |
mut+ |
instance- |
when called |
Notes:
-
imm* = applicable only on classes with all fields «»/«» and not «».
-
read* = applicable only on classes with all fields «»/«».
-
fields = method parameters are field names.
Capsule fields are seen as «».
-
fields+ = the first parameter is a «» field, seen as «». Additional parameters can be «»,«» or «».
-
invalidation = the method is executed on the first call, or the first call after invalidation.
-
invalidation+ = the method is executed during the factory, and immediately after any invalidation.
-
instance- = caches in the instance are invalidated.
-
read0 = a «» method with zero parameters.
-
mut+ = a «» method with the additional parameters as for fields+.
-
parallel = executed in a parallel worker starting after the factory.
Digressions / Expansions
As we have seen, parallel programming can be viewed as a form of caching.
In some cases, we need parallel programming on mutable data.
In our experience, this is not very common; the cost of copying data around is much smaller that most programmers assume.
Let us repeat this very clearly: there are many other ways to optimize software, and they are much easier and much,
much more rewarding than avoiding copying the few mutable parts of your data structure a couple of times.
We think that only highly skilled and motivated programmers
can discover and hunt down all of those other much more pressing algorithmic issues that
often spoil performance.
Only after approaching the performance limits of «» with good algorithms,
it could make sense to adopt parallel programming on mutable data to avoid some
extra clones.
However, if you are in such a situation,
you can use the annotation «» as shown below.
Again, the 42 type system will ensure that parallelism is not observable.
Like other
«» annotations,
«» is translated by
«»
into an actual implementation.
«» works only on methods whose body is exactly a round parenthesis block.
The initialization expressions for
«»,
«», and
«» are run in parallel, and the final expression is run only when
all of the initialization expressions are completed.
The method itself can take any kind of parameters, and they can all be used in the final expression, but the initialization expressions need to fit one of the following three safe parallel patterns:
Non-Mutable computation
In this pattern, none of the initialization expressions can use «» or «» parameters.
In this way nothing can be mutated while the forkjoin is open, thus parallelism is safe.
This is more expressive than «» since it allows us to run parallel code on «» references of mutable objects.
Single-Mutable computation
In this pattern, a single initialization expression can use any kind of parameter, while the other ones can not
use «», «» or «» parameters.
This pattern allows the initialization expression that can use «» to recursively explore a complex mutable data structure and to command updates to immutable elements arbitrarily nested inside of it.
Consider for example this code computing in parallel
new immutable string values for all of
the entries in a mutable list:
As you can see, we do not need to ever copy the whole list. We can update the elements in place one by one.
If the operation
«» is complex enough, running it in parallel could be beneficial.
As you can see, it is trivial to adapt that code to explore other kinds of collections, like for example a binary tree.
Or, in other words, if you are unsure on how to adapt that code to work on a tree, you should stick with
«» and accept that you are not (yet) one of the few elite programmers with enough skill to take advantage of the 42 fork join.
AdamsTowel could use metaprogramming features to define code that parallelises user defined operations on lists, maps and other common datastructures. However, we think this is beyond the responsibility of AdamsTowel, and should instead be handled by some user defined library.
This-Mutable computation
In this pattern, the 'this' variable is considered specially.
The method must be declared «», and the
initialization expressions
can not
use «», «» or «» parameters.
However, the parameter «» can be used to directly call
«» methods.
As we have seen before, «» methods can mutate a «» field; thus different initialization expressions must use «» methods updating different «» fields.
In this way, we can make parallel computation processing arbitrary complex mutable objects inside well encapsulated data structures .
Consider the following example, where «»s could be arbitrarily complex; containing complex (possibly circular) graphs of mutable objects.
This pattern relies on the fact that using
«» fields we can define arbitrary complex data structures composed of disjointed mutable object graphs.
Note that
«» aliases to parts of the data structure can be visible outside.
This is accepted since we can not access them when the forkjoin is open. The declarations can not use
«» parameters.
Interfaces, subtypes and matching
(1/5)Interfaces, Basis and Details
Interfaces Basis
Interfaces in 42 are quite similar to interfaces in other OO languages.
There are however a couple of important differences.
While implementing an interface method, you can avoid the
type signature.
For example, in the following code, to implement «» inside
of «», the types «» and «» are not repeated.
In 42, we say that the method
«»
implemented in
«»
is
declared by
«».
Each method is
declared at a single point.
Method declarations include parameter and return types.
A method can be
defined (that is, declared and implemented)
in the same class;
or declared in a (transitively) implemented interface and
then just implemented.
This means that a class cannot
satisfy multiple interfaces declaring methods
with the same method selector.
For example, this code is ill-typed:
Note that would be bad 42 code anyway; you should define an enumeration (or an alphanumeric)
for your cards and use a
«» unit of measure
for the time,
which would make the unavoidable type error more obvious.
Interface diamonds are allowed; that is, the following code is correct:
You can refine the return type of an interface method, by repeating the full type signature with the desired return type.
On the other hand, the parameter types cannot be refined.
(2/5) Interfaces and class methods
Interface methods in 42 are all abstract; that is, without bodies.
A version of the body will be provided by all classes implementing the interface.
This also includes class methods.
For example, consider the following code:
The pattern in the code above encodes the abstract factory
pattern in a much simpler way:
the binding
«» serves the role of
an instance of an abstract factory, and can create instances of
a specific kind of shape.
In 42 interfaces do not have
implemented class methods.
Sometimes we want to semantically associate some behaviour with an interface.
For example we could check intersections between shapes using
the draw method.
What we would need, is a traditional (non dynamically dispatched) static method.
In 42, static methods are just nested classes with a single class method with the empty name. In 42 adding new classes is very common, so do not be scared of adding a new class just to host a method.
For example
(3/5)Subtyping
Dynamic dispatch is the most important feature of object oriented languages.
Subtyping is the main feature supporting dynamic dispatch; for example
we can iterate over a list of «»
and «» them without the need to explicitly handle in the code all the possible kinds of
«» on a case by case basis.
Note that modifiers («»,
«»,
«»,..)) offer subtyping but not dynamic dispatch.
Interfaces and classes represent two fundamentally alternative compromises between
the providers and users of objects:
-
Interfaces allows client code to be implicitly parametric on the behaviour of individual objects.
The client code can make no assumption on the specific implementation of the interface.
-
Classes allow client code to rely on their invariants.
The user code is forced to pass only a specific kind of implementation.
In simpler terms, if we have a «» interface,
and we call «», we will get the right kind of drawing behaviour for that shape.
On the other hand, we can not statically predict what kind of shape and behaviour we will execute.
If instead we have a «» class,
then we can
statically predict what kind of behaviour «» will execute, and that the width and height of the drawn shape are going to be equal, as for the «» invariant.
On the other hand, our «» using code can only be used with squares, not any of the other kinds of shape.
(4/5)Matching
It is possible to inspect the runtime type of an object by using matching.
We will now provide various examples of matching and explain the behaviour.
We can use an
«» to check for the type of a local binding or an expression.
We can also check for more then one local binding at a time.
When an
«» checks for types it cannot have an
«» branch.
This
«» pattern allows us to write complex dispatch in a compact way, without the need of an explicit
«».
The syntax «» can also be used in expressions when the type system needs help with figuring out
the type, usually because some information will be generated by a decorator at a later stage. Sometimes we may have to write code like the following
«»:
the method
«» is called on the result of
«», that we expect to return a
«».
While «» will check the type at run time,
the explicit (sub-)type annotation with «» is checked at compile time, as if we wrote
«»
The code below:
is equivalent to:
That is: we can match out the result of any no-arg method into a variable with the same name, plus an optional numeric suffix.
This syntax can also be used inside of the
«».
This is often done for fields. Of course it works also to extract a single field:
As we have seen, matches are a form of syntactic sugar allowing more compact code for extracting subcomponents and taking decisions about their dynamic types.
(5/5)Interfaces, subtypes and matching, summary
Interfaces in 42 serve the same role that they serve in other languages,
with a little bit of a twist in the details.
The big news is that decorators («» in our examples) can
provide the boilerplate implementations for free.
This is much more powerful than traits, multiple inheritance or
Java 8 default methods, since the implementation can be generated by examining the class.
In the right conditions, matching is a useful tool to reduce code size and complexity.
However, dynamic dispatch is still the preferred option in the general case. On the other hand type matching works only on a finite
number of hard-coded cases, making it fragile with respect to code evolution.
Collection: list, map, set, optional and their operations
(1/5)lists manipulation
As we have seen before, lists can be defined using «», as in the example below.
As you can see above, many of the most common
«» classes have a nested class
called
«» as a convenience feature, to avoid having to define your own in most programs.
Lists can be created with square brackets; they are born mutable but can become immutable
if they are directly assigned to an immutable local binding or parameter, or by other forms of promotion; for example,
a method without
«» parameters returning a
«» reference
can be used to initialize an immutable binding.
You need to specify the type of the local binding to force the promotion.
For example:
Immutable lists can be combined with operators.
The general idea is that operators
«» work
on one sequences and one element,
while the corresponding doubled-up operators
«»
work on two sequences.
You can see the details of this below.
In addition of operators, immutable lists also support a plethora of methods:
As you notice, there are different kind of actions:
replace an element (
«»),
insert an element (
«»)
and skipping/filtering elements out (
«»).
Then, elements can be specified by index, by being
the leftmost or the rightmost.
To filter elements out,
you can also just provide the element.
Immutable collections (and also mutable ones, as we will see later)
can be accessed with the following methods:
Mutate sequences
Mutable sequences can be more efficient that
immutable ones, and are more general, since they
can store mutable objects.
Now we show some methods over a mutable list «»; consider each following line independently:
(2/5) Iterations on lists and views
We now show various pattens to iterate on lists.
First some usual foreach:
In 42 foreach allows to iterate on multiple collections at once, and also to update the collections:
In the example above, a dynamic error would be raised if
«»,
«» and
«» have different length.
We believe this is the right default behaviour.
To allow, for example,
«» to be longer then
«» and
«»,the programmer can
use some variants of
«»; a method producing an iterator on a subsequence of the original sequence.
The following variants are available:
«»,
«»,
«»,
«»,
«» and
«».
In the example below we use them to control iteration:
The class
«» provides flexible iterators and sub-sequences.
Consider the following code examples:
ys.size()
for x in NView(xs), y in NView(ys) ( .. )
//will iterate for as long as both xs and ys have elements.
//similar to a functional zip
}
NViewM = Collection.View(Num.List).more()
MainMore = {
xs= Num.List[..]
ys= Num.List[..]
for x in xs, y in NViewM(ys, more=30Num) ( .. )
//will iterate as long as xs, even if ys is shorter.
//y = 30Num when the iteration get over ys.size()
for x in NViewM(xs, more=10Num), y in NViewM(ys, more=30Num) ( .. )
//will iterate for as long as either of xs and ys have elements, and values
//x = 10Num, y = 30Num are used when the collections exhausted their elements.
for x in NView(xs), y in NViewM(ys, more=30Num) ( .. )
//behaves as in the "x in xs" case: a 'cut' view will consume 'more' elements
//if they are available
}
]]>
The power of the \
There are various methods taking advantage of the «» syntactic sugar.
They provide an expressive power similar to what list-comprehensions provide in python and streams in Java, but by just using simple control flow like for/if:
2Num \add(a) )
X[ bs1==Num.List[3\;4\] ]
//flatmapping
bs2 = Num.List()( for a in as for b in bs0 \add(a+b) )
X[ bs0==Num.List[11\;21\;31\;41\;12\;22\;32\;42\;13\;23\;33\;43\;14\;24\;34\;44\;] ]
//reduce to string
str0 = S"the content is: ".builder()( for a in as \add(a) )
X[ str0 == S"the content is: 1234" ]
str1 = S"[%as.left()".builder()( for n in as.vals(1I) \add(S", %n") )++S"]"
X[ str1 == S"[1, 2, 3, 4]" ]
//reduce/fold
acc = ( var x = 0Num, for a in as ( x+=a ), x )
acc1 = 0Num.acc()( for a in as \add(a) ) //also \addIf, \times, \val ..
X[ acc==acc1; acc1 == 10Num ]
//checks a property; great in an if or an X[]
ok0 = Match.Some()( for a in as \add(a>3Num) )
X[ ok0 ]
X[ !Match.All()( for a in as \add(a>3Num) ) ]
X[ !Match.None()( for a in as \add(a>3Num) ) ]
X[ 0I.acc()( for a in as \addIf(a>3Num) )==2I ]
asContainsAllBs = Match.All()( for b in bs \add(b in as) )
asIntersectBs = Match.Some()( for b in bs \add(b in as) )
asDisjointBs = Match.None()( for b in bs \add(b in as) )
//Note: b in as == as.contains(b)
]]>
The language 42 is expression based. Expressions that look like statements are just expressions with the
«» return type.
Those methods that take advantage of the
«» are simply methods with a single parameter
«».
The
«» method in the
«» examples short circuit when appropriate, so that the for can terminate as soon as the result is known.
(3/5) Lists with mutable and immutable elements
Mutable sequences can contain mutable objects.
While this can be useful in special circumstances, it can create aliasing issues similar to the
ones of the animals example of before.
To warn against such issues, methods «», «» and «» return
«» references to mutable objects. In order to obtain
a «» reference, the user needs to use the methods
«»,
«»
and «».
Up to now we focused on lists of «», but
all instances of «» are immutable; we now discuss what happens where
mutable lists contains a mixture of mutable and immutable elements.
Consider the following code:
As you can see, to insert a mutable point we need to use
«» and to
take the point out we have to add the
«» to the method.
When iterating on a list, if we expect a mixture of
«» and
«» values we must add
«»
to avoid a runtime error.
If we expect all values to be
«», we can write
«» instead.
When a
«» collection is promoted to
«», it still remembers what values were originally inserted as
«».
To make it so that all values can be read as
«», we can use the method
«». In addition of normalizing the collection, it also marks all values
accessible as
«», as shown in the code below:
(4/5) Map, set, opt..
«» also support maps, sets, optional and enumerations.
We will add more kinds of collections in the future.
Optional
In 42 there is no concept of null, and all the values are always intentionally initialized before
they can be read.
There are two main reasons programmers rely on nulls: optional values and circular/delayed initialization.
Circular initialization can be solved with a «» types, an advanced typing feature that we do not discuss here.
Optional values are a staple of functional programming and are logically equivalent to a collection of zero or one element, or, if you prefer, a box that may or may not contain an element of a certain type.
Optionals values can be obtained with «» as shown below.
Optionals are also optimized so that they do not require the creation of any new objects at run time.
//we can just check if a box is not empty as if it was a boolean
p01Box.#val().x(50\)//updates the x value of the point
Debug(S"printing %p01")//printing Point(x=50, y=1)
p00Box:= OPoint()//updates the local variables with empty boxes
p01Box:= OPoint()
if !p00Box ( Debug(S"printing %p00Box") )//printing <>
X[
!(p00 in p00Box);
!p00Box;//using isPresent() or not is just a matter of style
!p00Box.isPresent();
]
)
]]>
At this point in the tutorial, some readers will be confused that we can update the local variable binding
«» even if it is immutable.
Other readers instead will remember that immutability is a property of the reference and not of the binding/field: a local binding and fields declared
«» can be updated.
The updated value needs to respect the modifier of the field/binding type: if it is
«» it needs to be updated with another
«»; if it is
«» then it can be updated with either
«»,
«» or
«».
Oh, yes, another reader will realize ... and a
«» reference can be assigned to any of
«»,
«»,
«» or
«».
Note how both local bindings are updated using the same exact expression:
«»
In 42
«» can be either
«» or
«» (or
«» indeed)
On the other side, consider
«» and
«»: the first one is immutable since
«» is
«»,
while the second one is mutable since
«» is
«».
Optionals can be combined with the short circuting operators «» and «».
Many interesting patterns emerge from this:
Map
Thanks to normalization 42 can have very fast and most reliable hash sets and hash maps.
The values of sets and the keys of maps must be immutable, and are normalized just before being inserted in the collection.
Then, the value of the normalized pointer is used to check for equality and hashcode.
This has various positive effects:
-
The user does not need to write equality and hashing behaviour
-
There is no risk of mistake in the equality and hashing behaviour
-
The intrinsic invariants of the hashmap/hashset are never violated/corrupted.
-
The equality is a perfect structural equality, but is as fast as pointer equality; for maps with large keys this can make a massive performance difference.
Maps and sets have less methods than lists, but they can still be iterated upon, as shown in the following code:
%val") )
//we can use (..) to extract the key/val fields from PointToString.Entry
//this iteration is straightforward since all values are imm
roads = Roads[
key=S"Kelburn Parade", val=\(x=0\ y=0\); //immutable to immutable
key=S"The Terrace", mutVal=\(x=0\ y=0\);//immutable to mutable
key=S"Cuba Street", mutVal=\(x=0\ y=0\);//immutable to mutable
]
for read (key, val) in roads ( Debug(S"%key->%val") )
//we add 'read' in front to be able to read mixed imm/mut values
//if all the values were mutable, we could just add 'mut' in front
)
mut Roads.OVal optPoint = roads.#val(key=S"Cuba Street"))
optPoint.#val().x(50\)//update the field of the object inside the map
]]>
As you can see, when objects are retried from the map, we obtain an optional value; this is because statically we can not know if a key is mapped to a value or not.
We can use
«» or the
«» pattern to provide a default value if the key is not contained in the map.
In addition to conventional
«» and
«»,
maps offers the following methods:
-
To extract a value using the key:
«», «» and «»; to extract an optional
«», «» or a «» reference, respectively.
As for lists, it is always safe to extract a «» reference. An empty optional will be produced when attempting to extract as «» a value that was inserted as «» instead, so to reliably ask if a key is contained in the map we should write «».
-
Mutable maps can be modified by
inserting immutable values with «» and mutable values with «».
Finally, an association can be removed using «».
-
«» creates a class remembering the insertion order.
This is needed to make the iteration deterministic.
The keys can be retrieved with their order using
«» passing the desired «», from zero to «»
The corresponding value can be retrieved by methods «», «» and «»
to extract a
«», «» or a «» (not optional) reference to the value, respectively.
Set
Sets behave a lot like maps where the values are irrelevant, and have differently named methods.
In particular, in addition to conventional «» and «»,
sets offer methods «» and «» to add and remove an element,
and elements can be extracted in the insertion order by using method «»
We are considering adding operators «» to sets, as supported by lists.
On the other side, boolean methods like «» «» and «» can already be easily emulated with «» as we shown for lists.
(5/5) Collection summary
-
There are tons of methods and operators to know, but since most code works
around collections, it is worth the effort to memorize them.
-
Immutable collections are easy to play with, using operators and «» methods.
-
Mutable collections can be more efficient and flexible, but they come with additional difficulties.
-
Most methods have a general version that works with an index, and specialized «» and
«» variants.
-
«» can help remove a lot of boilerplate, but is a concept unique to 42, and require some effort to get used to.
-
«» is very useful and flexible. It is common to find methods composed from just a large
«» statement plus a little pre and post processing around it.
Digressions / Expansions
Collections support iteration with the «» syntax.
Iteration in 42 is way more flexible than in most other languages, and it is delegated on method calls.
Iteration in 42 is designed to support two main iteration strategy:
explicit indexes and iterator objects.
For example, the code
would expand to
Since
«» is declared
«»,
«» is used instead of
«».
Since
«» is declared
«» «» is used instead of
«».
Iteration methods in detail
-
«» and «»
They return an object able to provide the elements of the list. The second variant
returns a «» object, this is needed to provide the mutable version of those elements, and to update those elements in the list.
The second variant is used if the local binding is explicitly declared either «», «»,«» or «».
For complex bindings, like «», the second variant is used if any binding component would require it.
-
«» and «»
The initial iteration hint is produced by «» and moved forward by «».
-
«», «» and «»
The iterator checks if it has more elements to provide by calling «».
Since iterations in 42 can work on multiple collections at the same time,
«» results can be combined with «».
This design offers a lot of flexibility;
special kinds of collections may return special data types to coordinate termination in some way.
The AdamsTowel collections opt for an efficient solution where
there are four logical results for «»:
«», «», «» and «».
The iteration will stop if all the «» return «» or «»,
and an error is raised if some «» returns «» and some other returns «».
With this design, the result of the single «» method coordinates the various iterators to check if more elements are possibly available, and to decide how strongly to insist for those elements to be visited.
Possible implementations of Iteration methods
The iterator methods allows for a range of possible options.
The simplest one, and possibly the most efficient, is to delegate everything to the collection object:
in this case, there is no need to create a new object to serve as an iterator, since
the collection itself is able to simply access/update its elements by index, thus
«» and «» may simply return «».
«» returns the «»
and «» returns «» if «» and «» otherwise.
Finally, «» will throw error if «» would return «».
Another option would be the one of a linked list, where it would be inefficient to
rely on the index to access the elements.
In this case,
the «» and «» may simply return a singleton object
delegating the behaviour to the index object.
«» can simply expose the first internal
node, and «» can produce the next node.
The singleton object may be able to see private methods of those
internal nodes, thus even if we expose the internal nodes, they will be
just unusable black boxes to the library user, and all the interactions can be mediated, and checked by the singleton iterator object.
Iterators in Java and other languages will throw an error if the collection is somehow modified during the iteration. This could be supported by providing a specialized sublist object that can remember its version; but it is unclear if this is a good idea in 42, where most collections would be iterated while they are immutable.
The few cases of iteration on mutable collections
may be the ones where there are good reasons to perform mutation during iteration.
While adding elements at the start of a collection under iteration is most likely a bug, other operations have very reasonable use cases.
For example,
appending elements at the end of a list while computing a fixpoint, removing tasks from the end of a list if they are now unneeded,
or replacing already visited elements with new ones.
Input Output with Object Capabilities
(1/5) Controlling non determinism
Traditionally, in imperative languages I/O and side effects can happen everywhere, while
in pure functional languages like Haskell they are kept in check by monads.
In 42 we have type modifiers to keep mutation and aliasing under control.
With only the features shown up to now, 42 is a deterministic language, thus
every expression that only takes immutable objects
can be called multiple times with the same values and will produce the same result.
The whole caching system relies on this property to work.
Thus input output, random numbers and any other kind of observable non determinism must preserve this property.
Introducing object capabilities:
An object capability is a mutable object whose methods can do some non deterministic, or otherwise privileged operation.
If a method is given a «» reference to an object capability, then it can be non deterministic,
otherwise it will be deterministic.
Creation of new object capabilities is only possible by either relying on an existent object capability
or by using a capability method: a method whose name starts with «»; however those specially named methods can only be called from a main, from another capability method or from a «» method of a capability object.
(2/5) Example: File System
To read and write files we need to load a «» library as shown below:
«» is the local name for the library located at
«».
The
«» decorator embeds the library in the current environment.
We can now use our file system:
The crucial point in the former code example is the call to
«».
This instantiates a capability object using the capability method
«».
We could write the code inside a method in the following way:
Note how we pass the capability object explicitly to the method.
This is the most common style, and have great testing advantages:
Indeed,
«» corresponds to the following interface:
and
«» is simply an implementation of such interface connected with the real file system.
Thus, we can write a simple mock to check that the function behaves as expected:
(3/5) Object capabilities programming patterns
The advantage of the division of the file system in an interface and a «» implementation are not limited to testing.
For example, the user could embed some security and some restrictions in an alternative implementation of a file system.
Consider the following code:
Any code that would take in input a
«» would have a limited access to the file system; only able to read and write on
«» files.
Here we see for the first time the decorator
«».
«» explores all the nested classes of the decorated code, and if there is at least a
«» annotation, all the other members of such nested class will become private.
Methods implemented from interfaces are left untouched.
In the example above,
«» leaves the two factory methods visible and hides the field getter and exposer.
We discuss
«» more in the detail later.
Instances of
«» are capability objects; note how
«» can even declare a
«» method. In this way for the user there is no syntactical difference between using
«» or using
«».
Capability objects are a useful abstraction and can be designed and implemented by normal 42 programs; they are not just a way for the language implementation to expose native code.
We have just shown that new object capabilities can easy be defined by simple wrapping over existing capability objects.
Since inner is of type
«», this programming patterns allows us to layer many levels of security / restrictions on top of a capability object, as shown below:
(4/5) Connection with other languages
In general, all the non determinism in 42 is obtained by communicating with other languages.
42 allows us to connect with Java, and Java allows us to connect with C/assembly.
The best way to connect with java is to use the library «» as shown below:
The code above loads the library
«». It is a generic library: before being used we need to provide a name for the Java slave.
A 42 program is not a single process but a cluster of intercommunicating Java processes.
There is one master process where the 42 computation is actually running and many other slave processes allowing safe input output and safe interaction with arbitrary code.
Such slave processes have their own name: in this case
«».
Slaves also have a set of options, that can be specified between the
«».
We do not describe the details of those options here.
The class
«» can now be used to communicate with the Java slave as shown below:
| "any string computed in Java using "+id+" and "+msg);
| }
| }
""")
S.Opt text = j.askEvent(key=S"BarAsk", id=S"anId",msg=S"aMsg")
{}:Test"OptOk"(actual=text, expected=S"""
|<"any string computed in Java using anId and aMsg">
""".trim())
)
]]>
This code asks the event
«» on the channel
«».
The Java code registers the capacity of answering to the channel
«» and
computes an answer parameterized over
«» and
«».
The method
«» is synchronous: it will wait for Java to provide an answer as an optional string; optional since Java can return
«» as a result.
As you can see, you can embed arbitrary Java code in 42 and communicate back and forth serializing data and instructions as strings.
Synchronous communication is sometimes undesirable.
For example, to use Java to open a GUI it would be better to have asynchronous communication and a queue of events.
You can do this with
«», as shown below:
{
| System.out.println("Ping Event received ping "+msg);
| event.submitEvent("BarOut","fromJavaToL42","pong");
| });
| event.registerEvent("Kill",(id,msg)->{
| System.out.println("Doing cleanup before slave JVM is killed");
| System.exit(0);
| });
| }
| }
""")
model=Model(count=0I, j=j)
model.fromJavaToL42(msg=S"Initial message")
keys=S.List[S"BarOut"]
models=J.Handler.Map[key=S"BarOut" mutVal=model]
for e in j(keys) ( e>>models )
Debug(S"Completed")
)
]]>
The class
«» handles the events inside of 42:
if Java send an event with id
«» then the method
«» will be called.
In turn, such method
sends to java the message
«» on channel
«» using
«»
up to 40 times, and kills the slave JVM after that.
In
«» we initialize the slave JVM to respond to two channels:
«» and
«».
In our example Java will submit an asynchronous event to 42 as a response to
the
«» event and will terminate the slave on any
«» event.
The slave should always terminate its JVM when receiving a kill, but can do any kind of clean-up before that.
After a JVM is terminated, it can be restarted by simply calling
«» again.
Finally, we set up the event loop:
An event loop will collect events from a list of
«» and dispatch them
to a map of
«», mapping every key to a specific
«».
Note that both
«» and
«» are mutable objects. In this way we can dynamically
register and unregister keys/models by mutating
«» and
«».
If the JVM is killed or the list of keys becomes empty, the event loop
«» will terminate.
The operation
«>models]]>» dispatches the event to the model.
We need to use two different channels (
«» and
«») to distinguish if an event is should be handled by 42 or by Java.
(5/5) Object capabilities summary
-
While most languages run in a single process, 42 runs in a cluster of processes; this is needed so that the master process is protected from any slave process going into undefined behaviour.
This is the final piece of the puzzle allowing the correctness properties of 42 to be ensured in any circumstance.
-
To enable non deterministic behaviour we need to call those specially named «» methods.
Since they can only be called in a few controlled places, we can control what parts of the code can perform non determinism by explicitly passing capability objects.
-
Capability objects are a very convenient centralized point of control to inject security or other kinds of restrictions.
Digressions / Expansions
Non deterministic errors
When discussing errors, we did not mention how to handle errors happening in a non deterministic way; for example, how to recover when the execution run out of memory space.
In 42 this is modelled by non deterministic errors. They can only be caught in a main, in another capability method or in a «» method of a capability object.
AdamsTowel offers a single non deterministic error: «». When a non deterministic error happens, we can recover it by catching an «».
The code below shows how to cause a stack overflow and to recover from it.
That is, to recover from a non deterministic error we need to satisfy both the requirements of
«» non determinism and of strong error safety.
Aborting wasteful cache eagers
«» methods may return a cached result; such result is guaranteed to be the same that would be computed if we were to directly execute the method.
How does this work if the method is non terminating or simply outrageously slow?
Those eager cache methods will eagerly spend precious machine resources. If the results of those computations are ever needed by a main, the whole 42 process will get stuck waiting, as it would indeed happen if we were to directly execute the method. All good: in this case 42 correctly lifted the behavioural bug into caching.
However, if the result is never needed by a main, it would be nice to be able to stop those runaway pointless computations.
We can obtain this by calling «».
This works no matter if they are in loop, simply slow, or stuck waiting on another cache being slowly computed.
In some cases, we can even have multiple computations stuck waiting on each other in a circular fashion.
Exercises
A very large class of practically useful programs can be obtained
just by declaring
basic classes, collections
and simple Data classes.
Let's see some exercises and solutions
to better understand what 42 code looks like.
(1/5) Max method
Write a static method «» returning the max from a list of numbers
Basic Solution:
Solution using
«»:
Where the method
«» will already throw a meaningful error in case of an empty list:
«».
Defining your own error may still produce more readable errors, so feel free to mix and match the two approaches as show in the next exercise:
(2/5) Merging and filtering
Write a static method «» producing a string from two lists of strings of the same length.
For example
«»
should produce «z, b->y, c->z]"]]>»
Solution:
%v"))
if res.isEmpty() return S"[]"
text = res.reduce()(for s in \vals \add(\acc++S", %s"))
return S"[%text]"
}
}
]]>
Note how we write
«»
instead of
«» since
string interpolation does not currently support the
«».
Write a static method
«» filtering out from a list of strings the ones longer
than
«».
For example
«»
Precondition: «» is not negative
Solution:
= 0I]
S.List()(for s in that if s.size()<= size \add(s))
)
}
]]>
Again we see yet another way to handle errors; preconditions are appropriate when it is an observed bug if the user calls it with wrong parameters.
(3/5) Read/Write files
Write a static method «» returning the content of the file where the current source code is located.
As you can see, In the
«» parameter of
«» we can use the symbol
«» to specify holes in the expected string.
This is very useful to make more resilient tests.
(4/5) Random mole (and how to divide you code in multiple files)
Here we show a larger 42 example.
When writing large programs it is convenient to divide the source in multiple files.
In 42 this can be obtained with the «» symbol.
That is, if in a given file we write
42 will search for a file called either
«» or
«».
Such file has to contain valid 42 code; such code is replaced for the
«».
In this way code can be divided
in multiple files spanning a hierarchy of folders.
More precisely,
a 42 project must be a folder containing a file called «».
Folders can contain other files «» or folders containing other «» and other files.
The meaning of «» depend
on both the location in the code and
of the position of the current file in the file system.
To evaluate a «» 42 locates the nearest enclosing nested
library declaration, and we record its name («» in the example).
This identifies either a file or a folder.
If both or neither of these exist, there is an error.
Note that the found «» file can contain more «», which will be
resolved relative to the file before importing it into the current scope.
We can now design a longer example, represent a piece of land as a 80*80 bi-dimensional map,
where every cell can be full of dirt (90%) or rock (10%).
Then a mole start from the left top corner and attempts to
digs through dirt randomly.
After 3000 steps the mole stops.
Define the opportune classes and write a «»
method.
To start, we define some auxiliary classes:
=0I; x<80I;
y>=0I; y<80I;
]
method This go(Direction that) = {
return that.go(this)
catch error X.Guarded _ return this
}
method I index() =
(this.y()*80I)+this.x()
]]>
«» has an invariant ensuring that the coordinates are inside the 80*80 area.
We use
«» instead of
«» since
«» implements
«», thus we can rely on such error to trigger predictably.
We do this in method
«», where we capture the invariant failure in case moving the point would push it outside of the boundary; in that case we keep the point in the original coordinates.
«» is an enumeration, and we leverage on dynamic dispatch to encode the behaviour of the
«» method.
Note how in
«» we explicitly declared the top level
«» and
we implemented the outer level
«» explicitly in all the cases.
Then we could implemented the
«» method without repeating the type signature.
Instead, while declaring
«» we omit the
«» keyword and the explicit implementation.
Then we had to repeat the type signature each time while implementing the method
«».
Both ways to declare enumerations work and produce the same result.
Should we used separate files for
«» and
«»?
Their code is quite short, so we chose not to.
If in the future we had many more kinds of Cells, we could move that code in its own file later.
«» has a
«» field and a
«» field; a list of cells of size 80*80.
While we expect the user to provide the random object, we wish to provide a way to initialize the
«».
In 42, as for most languages, we could provide a default value for a field by writing
«», but in this case we need to use the provided random object
to complete the initialization, thus we use a
«»
instead.
As with
«»,
we take in input parameters with the same name of the fields we wish to use.
«» is going to rely on this method to initialize the
«» field.
This is actually a general pattern of
«», allowing default values for any method.
The method
«» is straightforward.
Note how we override
«» instead of accepting the implementation provided by
«».
To use the
«» class we can use the code below.
(5/5) Examples summary
-
Always think about what can go wrong upfront
-
Many methods can be completed by first checking for
errors/issues and then using a «», possibly inside a «» or a «».
-
Before heading into a problem,
spend some time to define your problem domain.
We dodged a lot of headaches by defining
points with invariants.
-
Well organized code, properly divided in files and folders is much easier to navigate and maintain.
Finally, Metaprogramming
Metaprogramming is the most important feature of 42.
All the decorators that you have seen up to now are implemented with metaprogramming,
which shows that 42 offers a good balance of freedom and safety.
The main idea of 42 metaprogramming is that only library literals can
be manipulated.
Metaprogramming is evaluated top down, most-nested first.
Once a library literal has a name, it can not be independently metaprogrammed; but only influenced
by metaprogramming over the library that contains it.
«» is a decorator allowing to store code in a reusable format.
«» is a decorator that extracts the code from a trait. For example
In this code we show that
«» contains all the code of both
«» and
«».
Note how the abstract
«» in
«» is merged with the corresponding implemented method in
«».
Traits allow us to merge code from different sources, as it happens with multiple inheritance.
However, traits are flattened: The code is actually copied in the result.
Traits are boxes containing the code and offering methods to manipulate such code.
A trait can be created by doing either
«» or
«».
The trait object has a method
«» returning the contained code.
Trait content can be composed using the operator
«» (as shown above) or
«».
For traits there is no difference in behaviour between
«» and
«», but the operator precedence and associativity is different.
Simply composing traits allows us to emulate a large chunk of the expressive power of conventional inheritance.
For example, in Java we may have an abstract class offering some implemented and some abstract methods.
Then multiple heir classes can extend such abstract class implementing those abstract methods.
The same scenario can be replicated with traits: a trait can offer some implemented and some abstract methods. Then multiple classes can be obtained composing that trait with some other code implementing those abstract methods.
(2/5)Trait composition: methods
Trait composition merges members with the same name. As shown above, this allows method composition.
Also nested classes can be merged in the same way: nested classes with the same name are recursively composed, as shown below:
Fields?
But what about fields? how are fields and constructors composed by traits?
The answer to this question is quite interesting:
In 42 there are no true fields or constructors; they are just abstract methods serving a specific role.
That is, the following code declares a usable «» class:
That is, any
«»,
«» or
«» no-arg abstract method can play the role of a getter for a correspondingly named field, and any abstract
«» method can play the role of a factory, where the parameters are used to initialize the fields.
Finally,
«» methods with one argument called
«» can play the role of a setter.
Candidate getters and setters are connected with the parameters of candidate factories by name.
To allow for more then one getter/setter for each parameter, getters/setters names can also start with any number of
«».
We call those abstract methods
Abstract State Operations .
In Java and many other languages, a class is abstract if it has any abstract methods.
In 42, a class is coherent if its set of abstract state operations ensure
that all the callable methods have a defined behaviour; this includes the initialization of all the usable getters.
In more detail, a class is coherent if:
-
All candidate factories provide a value for all candidate getters, and all the types of those values
agree with the return type of the corresponding getters.
The parameter type of all candidate setters agrees with the return type of the corresponding getters.
-
Additionally, any non-class method can be abstract if none of the candidate factories return a value whose modifier allows to call such a method.
In particular, this implies that if a class has no candidate factories,
any non class method may be abstract, as shown below:
A main can call class methods only on coherent classes that only depend from other coherent classes, thus for example
The decorators
«» and
«» also checks for coherence: the following application of
«»
would fail with the following message:
The class is not coherent. Method bar() is not part of the abstract state .
We can use
«» and
«» to suppress this check when needed.
Indeed
«» behaves exactly as
«».
Earlier in this tutorial we have shown code like
In that code
«» looks like a conventional field declaration, but it is simply syntactic sugar for the following set of methods:
Then,
«» will discover that
«»,
«» and
«» are good candidate fields and will add factories
and a lot of other utility methods.
(3/5)Nested Trait composition: a great expressive power.
Composing traits with nested classes allows us to merge arbitrarily complex units of code.
In other languages this kind of flexibility requires complex patterns like dependency injection, as shown below:
As you can see, we can define more code using
«» while only repeating the needed dependencies.
We will use this idea in the following, more elaborated scenario:
Bob and Alice are making a video game. In particular, Alice is doing the code related to loading the game map from a file.
As you can see from the non modularized code above, Alice code is tightly connected with Bob code:
She have to instantiate
«» and all the kinds of
«»s. In a language like Java, Alice would need to write her code after Bob have finished writing his, or they would have to agree to use dependency injection and all the related indirections.
Instead, in 42 they could simply factorize their code into two independent traits:
Now that the code of Alice and Bob are separated, they can test their code in isolation:
%weight")
}
Wall = /*..*/
Map = {
var S info
class method mut This (S info)
class method mut This empty() = \(S"")
mut method Void set(Item i) = this.info(\info++i.info()++S.nl())
}
}
TestAlice = (
files=FS.#$()
{}:Test"justARock"(
actual=MockAlice.load(files=files, fileName=S"justARock.txt")
expected=S"""
|Rock: Point(5,6) -> 35
""")
{}:Test"rockAndWall"(
actual=MockAlice.load(files=files, fileName=S"rockAndWall.txt")
expected=S"""
|Rock: Point(x=5, y=6) -> 35
|Wall: Point(x=1, y=2) -> 10
""")
..//more tests here
)
]]>
(4/5)Typing considerations
Object oriented programs often contain entangled and circular type definitions.
For example, strings «» have methods «» and «», while
both «» and «» offer a «» method.
That is, while circular values are a double edged sword (useful but dangerous), circular/recursive types are unavoidable even in simple programs.
So, how do recursive types interact with metaprogramming?
Path names can only be used in execution when the corresponding nested class is fully typed,
thus the following example code would not work:
We can not start computing
«» since
«» depends from
«», that is defined later.
Swapping lines would not help, since
«», in turn, depends from
«».
Later we will learn how to overcome this issues and still generate code with the intended structure.
As shown below, library literals can instead be manipulated even if
they are not fully typed.
Here
«» can be computed even if
«» is still unavailable.
However, such a manipulation must happen in place: we can not use traits to reuse untyped code; that is, the following would not work:
«» can not be used before
«» is defined.
This also allows us to avoid defining many redundant abstract methods.
Consider the following working code:
This code is allowed even if
«» does not contain an abstract definition for
«».
This feature is often used by 42 programmers without even recognizing it, but it is brittle:
when method calls are chained (as in
«») or when binary operators or type inference are involved, the system needs to be able to guess the return type of those missing methods.
(5/5)Metaprogramming summary
Here we have introduced the way that 42 handles metaprogramming and code reuse.
We focused on «» and «».
Composing code with «» or «» we can partition our code-base in any way we need,
enabling simple and natural testing and code reuse patterns.
When reusing code, we have to be mindful of missing types and missing methods. Structuring the code-base to avoid those issues will require some experience, and is indeed one of the hardest parts about writing complex 42 programs.
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.
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.
Example of a 42 program
In this chapter, we will see how to develop a GUI connected with a DataBase.
For simplicity, consider that we have a database with a single table «» that contains a few fields.
We want to make a GUI that displays the data and allows us to edit it.
(1/5) Query boxes
First we load the libraries we need:
Unit, JavaServer, GuiBuilder and Query.
We then declare some useful units.
«» objects have ages expressed in years,
heights expressed in meters and weights expressed in Kgs.
We then ask «» to generate a class to support SQL queries using a Java slave and a connection string.
As an example, we are using a derby DB.
For now, we consider the DB to be already populated. In the end we discuss how to initialize the DB itself.
The class
«» can now reify the DB tables; here it is just
«».
Finally, we can make a query box with all the queries that are relevant for this application.
A query box is a capability class, whose methods are able to do queries on a given database.
It is obtained with the decorartor
«»,
that can recognize nested classes created with the
«» method.
Since
«» was created by providing the connection string, queries are aware of their database.
The symbol
«» identifies parameters in the queries, while the types
in
«» are the query result type followed by any parameters.
Queries return lists of objects. Those objects are constructed by calling a (factory) method whose arguments have the same name as the query fields.
Right now the class «» supports both «» and «».
We expect to add more query languages in the future.
«» is a query language to query the user by asking them to complete a form.
Using «» in 42 is very similar to using «».
In particular, the result of both SQL and IQL queries is a lists of objects instantiated using a unique #immK(..) method.
While this is a consistent and flexible way to process tabular data,
it means that for queries returning a single column we must have a
type with such a single field.
In 42, declaring those types and their corresponding list type takes just a single line.
Note how for Person we can use our specialized units «», «» and «».
In the same way, if a query returns a single row, we will have it served as the only element of a length 1 list.
We can now make the set of all user queries with another «»:
For a complete guide to IQL, you can refer to the well designed
IQL guide, located in the readme of the
IQL repository.
(2/5) Model
To write a GUI we need a Model and a View.
The model is an object whose methods are triggered by events of the view.
Comparing this with the conventional MVC, here the model serves both the roles of the model and the controller.
In this example, the model will have the two boxes and the java slave to control the Gui.
Methods annotated with
«» will respond to the corresponding event from the view.
Those methods must all take a single
«» parameter, used by the view to communicate extra information. This parameter is often unused.
Those methods are defined as follows:
«» first asks the view to clear the table; then
it executes the query
«» by doing
«».
That is:
«» is the
«» offering access to all the individual query objects.
«» is the field access for the query object doing the
«» query.
Finally, since this query takes no parameters, we just use
«» to call it. Calling the query returns a
«» object,
that is iterated with the
«». We map the fields of
«» onto local variables
«» for easy access in the
«» body.
For each
«», the body asks the view to add a line into the table. Information has to be encoded as a string to be passed to the view. String interpolation
«» make this easy, and
«» values are converted as
«» to print them with decimal points instead of printing them as a fraction.
Finally, we
«» exceptions to assert that we do not expect them to be leaked.
This is quite a mouthful.
42 code tends to be quite compact, but for the sake of clearity and to support learning, we will now encode it again in a more verbose style:
As you can see, you are free to make the code more readable by declaring a lot of local variables, but you can also keep a more compact style.
In the end, more verbose code may end up less readable simply because there is much more of it.
The other methods have a similar structure.
The method
«» ask the user to provide data for a list of persons by running the
«» IQL query,
returning a
«».
Then, the data of each
«» is inserted in the database.
Note how the parameters of the query
«» are provided using the names of the query declaration
«»:
«» in the query string was used to create the
«» parameter; and so on for the others. Thanks to metaprogramming
the query method is synthesized out of the query string.
After inserting all the new data in the database, we refresh the displayed table by manually calling
«».
Methods
«» and
«»
are very similar; they call the corresponding
«» query,
extract the single field (using the notations
«» and
«») and update the database using the corresponding
«» query.
In this setting we represent persons in two different classes:
«» and «».
This allows for those two different classes to actually be quite different:
«» have an «» field and fields «» and «» are of type «».
On the other side «» have no «» field and
fields «» and «» are of type «» and «».
Those two classes serve different roles, and if we wish to change the
kind of data the user must provid we can change «» and make it even more distant with respect to the information stored in the database.
On the other side, if we wanted to apply a transformation on the data readed from the DB, we could use another custom person class, instead of «», and define and appropriate «» method to adapt the data from the database into any shape we need.
It is also interesting to consider what happens if the database schema changes.
If the person table is removed, or the person fields are renamed,
then we will get an error while typing the model.
In some sense we are turning events that would have caused a runtime exception into
understandable compile time errors.
(3/5) View
The library «» allows to write a GUI
using Java Swing.
For safety reasons, Java code is compiled and executed on a separated JVM.
We can easily generate a GUI for our example in the following way:
SwingUtilities.invokeLater(()->tModel.addRow(msg.split(","))));}
|{event.registerEvent("Example.Display","tableClear",
| (k,id,msg)->SwingUtilities.invokeLater(()->tModel.setRowCount(0)));}
"""
)
]]>
Where
«» is a convenient way to generate a button raising 42 events.
When such 42 code will run, the following Java code will be generated:
new l42Gui.L42Frame(event,"Example",800,600){
JPanel screen1=new JPanel();
{add(screen1);}
JPanel buttons=new JPanel();
{addNorth(screen1,buttons);}
JButton addPerson = new JButton("add");{
addPerson.addActionListener(e->event
.submitEvent("Example","addPerson","PressedAdd"));
}
{addFlow(buttons,addPerson);}
JButton removeById = new JButton("remove by id");{
removeById.addActionListener(e->event
.submitEvent("Example","removeById","PressedRemove"));
}
{addFlow(buttons,removeById);}
JButton removeByName = new JButton("remove by name");{
removeByName.addActionListener(e->event
.submitEvent("Example","removeByName","PressedRemove"));
}
{addFlow(buttons,removeByName);}
JButton printAll = new JButton("printAll");{
printAll.addActionListener(e->event
.submitEvent("Example","printAll","PressedPrint"));
}
{addFlow(buttons,printAll);}
Object[] tLabels={"id","name","age","height","weight"};
DefaultTableModel tModel=new DefaultTableModel(new Object[][]{},tLabels);
JTable table = new JTable(tModel);
{addCenter(screen1,new JScrollPane(table));}
{event.registerEvent("Example.Display","tableAdd",
(k,id,msg)->SwingUtilities.invokeLater(
()->tModel.addRow(msg.split(","))));
}
{event.registerEvent("Example.Display","tableClear",
(k,id,msg)->SwingUtilities.invokeLater(
()->tModel.setRowCount(0)));
}
}
);
}
}
]]>
and in the java main
«»
will be called.
As you can see, the code provided by the user is simply injected into
the body of a
«» class.
From that context we can declare fields and methods, and we can declare initialization actions using (non-static) java initialization blocks.
This code would look trivial if you are a Java Swing expert, and very obscure otherwise.
Note how we use
«»
to add the row to the table: in java
«» wants an array
and
«» will produce an array from a string whose parts are separated by
«».
We made in this way for the sake of a simple example, but
we are unsatisfied by this brittle solution: it only works since names or numbers should not have the
«» inside.
(4/5) Putting all together
Finally, a «» puts this all together
>model )//event loop
)
]]>
As you can see, the GUI produces events on the channel
«» (the name of the generated Java class) and
consumes events on the channel
«».
If we wanted to add functionalities to initialize and
to clear the database, we could do as follow:
(5/5) Summary
42 metaprogramming allows for complex applications to be written in compact and secure ways:
in this application we used
«»,
«»,
«» and
«».
Those are all normal 42 libraries that 42 programmers could write themselves, and indeed studying the implementation of those libraries is currently the best way to become a Magrathean.
In particular, «» allows us
to take queries written in another language (for now, just SQL and IQL, but the concept is expandable)
and converts them into a simple 42 well typed API that can be used
to build programs in an compact and elegant way.
Concepts like the «» can be used to control what part of an application is allowed to do important operations, adding a great deal of security.
Deploy 42
In the context of 42 and AdamsTowel, there are three things that can be deployed:
Executable programs, Towels and Modules.
(1/5)Deploy Towels
A towel is about the most massively useful thing a programmer can have.
A towel has immense psychological value, and you should always know where your towel is.
All the classes that we have used up to now without defining them, are defined in AdamsTowel.
They are all normal classes/libraries.
You can code without a towel, but this means starting from first principles,
which could be quite unpleasant; especially since the only
primitive things that 42 offers are Library literals
(code as first class entities), the constant «»,
and the types «», «» and «».
Towels are libraries providing standard
functionalities and types, such as number, boolean,
string and various kinds of decorators and system errors.
However, we do not expect all 42 programs to reuse the same exact towel.
For hygienic reasons, in real life everyone tends to use their own towel.
For similar reasons, any sizeable 42 program will use its own towel.
We expect different programs to use massively different libraries for
what in other languages is the standard library.
That is, there is no such thing as 'the 42 standard library'.
Using multiple Towels
Towels shines when multiple towels are used at the same time.
Different code parts build upon different set of classes.
That is, by introducing multiple towels in nested scopes,
the names of the other scopes are
masked.
This is very useful for code that reasons on code; such task is pervasive in 42.
Staining Towels
If you are writing a sizeable program,
or many similar programs, it make sense to
enrich a towel with some pre loaded libraries
and basic classes.
If you have write access to a github project under
«», the former code will create your towel and update it
on your github repository every time you run it.
If you want to just write on your hard drive, you could just do
We are considering adding more variants, for example to allow writing on your FTP servers, google drives, dropbox and other similar services.
A Stained Towel is a towel that looks like another but it is enriched by adding more things, either at the bottom.
In our example, «» is just a stained variation of «».
(2/5)Module deployment
If you start writing in 42, you will soon feel the need
to factorize your project into libraries that can
be independently tested, deployed and loaded.
We call those library Modules.
Very successful modules are used by multiple
independent projects and developers; they are what is often called a third party library.
However, most modules exists just as development tools in
order to keep the complexity of big projects under control.
In 42 it is easy to code with multiple modules, and modules can be much smaller than usual third party libraries and frameworks in other languages.
In 42 it is possible to employ a programming model where every developer (or every pair of developers in a pair programming style) is the
only one responsible of one (or more) modules and their maintenance process, while the group leader gives specifications and tests to be met by the various module developers and will glue all the code together.
Modules can be deployed in a way similar to towel deployment;
«» is used to load libraries,
but it also contains all the knowledge to deploy
them.
The following example code deploys a Module
using «»:
This code deploys
«» to an URL as a module,
and turns
«» and any other
nested classes stained on top of
«» private.
This includes
«»,
«» and
«» from
«».
If there were any nested classes unreachable from public classes inside
«» it will be pruned away.
Same for any nested class stained on top of
«» and for
any private unreachable one in
«».
The deployed library can be imported as usual.
For example the main
«»
allows us to see the content of «».
All the deployed code is closed code.
Towels are closed because they contain all the code to implement strings, numbers and so on.
Modules are also closed. They have abstract classes/methods
for each of the original towel concepts (before staining), and they can be rebound
to a multitude of towels.
In particular all stained versions of the same towel are compatible.
Every needed nested library
that was not present in the original towel, will be made private.
On the other side, all the classes in the original towel will
be made abstract by «»
and will be rebound to the current towel by «».
Thus, in our example,
«»,
«»,
«» and «»
would become a private implementation detail of the exposed library.
(3/5)Deploy programs
We can run our applications inside 42, but we can also deploy them as Jars, so that they can be run as a Java application.
In 42 libraries can be directly manipulated, and
one possible manipulation is to convert them in
another format, like an executable jar or a native program
and then save the result somewhere, such as on the website where the users can download it.
For example, we could rework the code of the former chapter as follows:
>model )
)
}
//..
Tast = DeployGit.jar(ToJar()
on=Url"github.com/Bob/Modules42/MyApplication.jar"
writer=GW.#$of(token=Secret.#$of(),message=S".."))
]]>
As you can see,
we are wrapping the application code into a trait, including
a second reuse of
«».
In this way the code of
«» is fully self contained, and
«» in the outer scope can still use all of the towel features by taking them from the outer
«».
We then use
«» to deploy our application onto a jar in a specific location.
Again, we could just deploy it on our file system or on another kind of service by using another kind of
«».
When 42 is used to deploy an application as a Jar, you can see the whole 42 execution as a comprehensive compilation framework,
encompassing all the tools and phases that could possibly be needed into a single cohesive abstraction.
Such jar can be run with the following command
«»
In this example we reuse AdamsTowel both outside «»
and inside of it.
The two towels do not need to be the same.
The outermost just has to support the deployment process
«», while the inner one is needed to make
«» a closed library: only libraries that do not refer to external classes can be deployed.
A 42 project testing and deploying
A common way to use 42 is to have a folder with the name of your project, containing
a folder «» with all the actual code,
and then various files providing testing and deploying functionalities, as in the following example:
In general, for medium size projects is a good idea to keep executing the tests before the deployment; for example
we can have a test suite after the
«».
Do not panic, If the test are not reachable from
«», they are not going to be included in the executable jar.
(4/5)Towel embroidery: Define and deploy our own towel
Towel embroidery it is like adding your initials to your towel.
While we can simply add to the end by staining, embroidery is much more powerful.
The most common embroidery tool
is «».
Together with late casts, we can add methods to any existing class as shown below:
The advantage with respect to composing two separated
libraries is that the scope is the same:
the implementation of
«» will be able to use
«»,
«» and so on.
Towel staining is a very minimal personalization, and stained towels are fully compatible with the original one.
With embroidery you can personalize the content of your towel a lot more,
but when module deployment
relies on an embroidered towel, compatibility with the original towel is lost.
For example, an embroidered version of
«»
can
«» a library developed on the original
«», but a library developed on the embroidered version
needs to be loaded into a similarly embroidered towel.
One typical reason to embroider a towel is to
extend the set of classes that are shared between libraries.
For example, one may want to develop a Towel for scientific use
where the existence of some units of measure can be shared between all the libraries.
We could do the following:
Num]:{@AbstractTowel{
"en.wikipedia.org/wiki/International_System_of_Units"}}
Load$={
class method Introspection.Nested.List _baseDeps()
class method Introspection.Nested.List baseDeps() = this._baseDeps().withAlso(\[
Info(Unit);
Info(SI);
])
}
}
Secret = {...}
GW = Load:{reuse [L42.is/GitWriter]}
LoadDeploy = Load:{reuse [L42.is/Deploy]}
DeployGit = LoadDeploy.with(writer=GW)
DeployRicherTowel = DeployGit.towel(
Organize:RawCode
['Load.baseDeps()->'Load._baseDeps()]
[hide='Load._baseDeps()]
on=Url"github.com/Bob/Modules42/SITowel.L42"
writer=GW.#$of(token=Secret.#$of(),message=S".."))
]]>
Now
«» can be used as a towel,
and can be used to deploy modules that can be loaded by
«».
By using semantic URIs as
ontological nodes, we
can create a basis for other libraries when trying to infer the meaning of our added types.
In the example above we used wikipedia links to relevant concepts.
This may not be the best solution, devising a good solution for this problem would require very intense research in the area of Ontological mapping.
«» can be used now to deploy and load libraries wrote in
«», and libraries deployed and loaded in this way will
share a unique definition for certain units of measure.
Note that libraries originally developed for
«» can still be loaded normally since «» is structurally a superset of «».
(5/5)Deployment: programs, libraries and towels; summary
-
42 is a metaprogramming tool.
It is natural to use 42 either as a language (to run a program)
or as a compiler (to deploy programs, libraries and towels).
-
Indeed we expect all sizeable 42 projects to use 42 as a compiler,
to produce some reusable artefacts.
-
The distinction between towels (that do not need «»)
and modules is
introduced not by 42, but by «»; radically different towels may provide different meaning for the concepts of deploying and
loading libraries/towels.
-
Application developers can freely stain and embroider towels;
in this way they can adapt «» to serve them better.
However, library developers need to carefully consider the effect of embroidery.