3.5 Sequencing

So far, we have combined mathematical expressions using mathematical operators. For example, from the expressions sin(x) and cos(x), we can calculate their division using sin(x)/cos(x). This is possible because the evaluation of the sub-expressions sin(x) and cos(x) will produce two values that can then be used in the division.

With geometric functions, instead of combinations of values, we are mostly interested in combinations of side effects. To this end, Julia provides a way of producing them sequentially, i.e., one after the other. For example, consider a function that draws a circle with a radius \(r\), centered on the position \(P\), with an inscribed or circumscribed square, depending on the user’s specification, as we show in this figure.

A circle with an inscribed square (left) and a circumscribed square (right).

image

The function’s definition could start as something like:

circle_square(p, r, inscribed) =

  ...

The drawing produced by the function will naturally depend on the logic value of inscribed, which means that this function’s definition could be something like:

circle_square(p, r, inscribed) =

  inscribed ?

    creates a circle and a square inscribed in the circle :

    creates a circle and a square circumscribed in the circle

The problem now is that, for each case of the conditional expression, the function must generate two side effects, namely, to create a circle and to create a square. However, the conditional expression only admits one expression, making us wonder how we can combine two side effects in a single expression. For this purpose, Julia provides the concept of compound expression. Any sequence of expressions can be transformed into a compound expression by separating them with semicolons and wrapping the result in parentheses, for example:

> 1+(2*3; 4*5)

21

A compound expression can take any number of expressions, and its evaluation will sequentially evaluate each of them, i.e., one after the other, returning the value of the last one (as is visible in the previous example). Logically, if only the value of the last expression is used, then the values of the other expressions are discarded and these expressions are only relevant for their side effects.

In order to make compound expressions more visible, Julia also supports a different syntax based on the use of the words begin/end. In this case, it is also possible to avoid the semicolon by placing different expressions in different lines. For example:

> 1 + begin 2*3; 4*5 end

21

> 1 + begin

        2*3

        4*5

      end

21

Using compound expressions, we can further detail our circle_square function using either

circle_square(p, r, inscribed) =

  inscribed ?

    (creates a circle;

     creates a square inscribed in the circle) :

    (creates a circle;

     creates a square circumscribed in the circle)

or

circle_square(p, r, inscribed) =

  inscribed ?

    begin

      creates a circle

      creates a square inscribed in the circle

    end :

    begin

      creates a circle

      creates a square circumscribed in the circle

    end

Interestingly, the if form described in section Multiple Selection also allows further simplification, as it includes an implicit begin/end on each clause, meaning that the previous function can be written as:

circle_square(p, r, inscribed) =

  if inscribed

    creates a circle

    creates a square inscribed in the circle

  else

    creates a circle

    creates a square circumscribed in the circle

  end

All we need to do now is translate the remaining sentences into the corresponding Julia expressions. In the case of the inscribed square, we will use polar coordinates because we know its vertices will be set in a circle (the top right vertex at an angle of \(\frac{\pi}{4}\) and the bottom left vertex at an angle of \(\pi+\frac{\pi}{4}\)).

circle_square(p, r, inscribed) =

  if inscribed

    circle(p, r)

    rectangle(p + vpol(r, 5/4*pi), p + vpol(r, 1/4*pi))

  else

    circle(p, r)

    rectangle(p - vxy(r, r), p + vxy(r, r))

  end

An attentive look at the previous function circle_square shows that a circle is always created independently of the square being inscribed or not. That way we can redefine the function so that the circle is created outside the conditional expression:

circle_square(p, r, inscribed) =

  begin

    circle(p, r)

    if inscribed

      rectangle(p + vpol(r, 5/4*pi), p + vpol(r, 1/4*pi))

    else

      rectangle(p - vxy(r, r), p + vxy(r, r))

    end

  end

Julia allows a final simplification based on the use of an alternative syntax for function definitions: instead of

name(parameter1, ..., parametern) = body

we can use

function name(parameter1, ..., parametern)

  body

end

Using this last syntax, we have:

function circle_square(p, r, inscribed)

  circle(p, r)

  if inscribed

    rectangle(p + vpol(r, 5/4*pi), p + vpol(r, 1/4*pi))

  else

    rectangle(p - vxy(r, r), p + vxy(r, r))

  end

end

To sum up, Julia provides two different but equivalent syntaxes for function definitions, for conditional expressions, and for compound expressions. We should use the one that, for each particular case, makes the program easier to understand.