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).
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.