Previous ... Next

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.

      Previous ... Next