On this page:
9.3.1 Exercises 42
9.3.1.1 Question 161
9.3.1.2 Question 162
9.3.1.3 Question 163

9.3 Anonymous Functions

The possibility of using higher-order functions opens up a huge range of new applications. For example, if we want to calculate the summations \[\sum_{i=1}^{10}i^2+3i\] \[\sum_{i=1}^{100}i^3+2i^2+5i\] we only need to define the functions \(f_1(x)=x^2+3x\) and \(f_2(x)=x^3+2x^2+5x\) and use them as argument of the summation function:

f1(x) = x*x+3*x

f2(x) = x*x*x+2*x*x+5*x

> summation(f1, 1, 10)

550

> summation(f2, 1, 100)

26204450

As can be seen, we can easily calculate summations of different functions. However, there is a drawback: if, for each summation we intend to calculate, we have to define the function in question, we will pollute our Julia environment with countless definitions of functions whose single purpose is to serve as argument of the summation function. We will also have to come up with names for all of them, which could be difficult, particularly, when we know that these functions only suit one purpose. In the last two examples this problem was already visible: the names f1 and f2 only reveal the difficulty of finding more expressive names.

In order to solve this problem, we should examine in greater detail the concept of defining a function. So far we have said that, for defining a function, we have to assign the function’s name and parameters to the function’s body. For example, for the function \(x^2=x\times x\), we write:

sqr(x) = x*x

After its definition, the sqr symbol becomes associated with a function:

> sqr

sqr (generic function with 1 method)

On the other hand, we saw in section Global Variables that in order to define constants we have to assign the name we want to define to the expression that we intend to associate with that name. For example:

phi = (1+sqrt(5))/2

After its definition, the phi symbol becomes associated with a value:

> phi

1.618033988749895

By analyzing these examples we can conclude that the only difference between the definition of a function and the definition of a constant comes down to how the defined value is obtained. In the definition of a constant, the value is the result of the evaluation of an expression. In the definition of a function, the value is a function that is constructed from the description of a list of parameters and the body of a function. This leads us to think that if we could have an expression whose evaluation produced a function, then it would be possible to define functions as if we were defining constants. Given that it is the definition that names the function, the function produced by the expression is unnamed and, in fact, it is called an anonymous function. An expression that evaluates to an anonymous function is known as a lambda expression. The name lambda expression derives from the lambda calculus (or \(\lambda\)-calculus), a mathematical model for computable functions, i.e., functions whose invocation can be evaluated mechanically.

In Julia, lambda expressions have the following syntax:

(parameter1, ..., parametern) -> expression

When the function has only one parameter, the syntax can be simplified to:

parameter -> expression

Notice the following:

> x -> x*x

#15 (generic function with 1 method)

> my_sqr = x -> x*x

#17 (generic function with 1 method)

> my_sqr(3)

9

As you can see, the evaluation of the lambda expression returns something that indicates that a procedure was created. When we associate the anonymous function with a name, that name becomes the designation of the function, and can be used as if it had been originally defined as a function. In practice, the definition of functions using lambda expressions is equivalent to the regular definition of functions. This equivalence can be easily tested with the redefinition of functions that, now, can be defined as an association between a name and an anonymous function. For example, the function that calculates the area of a circle can be defined by either:

circle_area(radius) = pi*radius^2

or:

circle_area = radius -> pi*radius^2

Although from the syntactic point of view, the form f(___) = ___ is equivalent to f = ___ -> ___, there is a semantic difference with important implications: the first form does not evaluate any of its arguments, whereas the latter form evaluates its second argument. This allows the second form to define functions with more sophisticated forms.

The use of anonymous functions has another advantage that becomes evident when we analyze the function_points function:

function_points(p, f, alpha, beta, gamma, x0, x1, dx) =

  if x0 > x1

    []

  else

    [p+vxy(x0, f(alpha, beta, gamma, x0)),

     function_points(p, f, alpha, beta, gamma, x0+dx, x1, dx)...]

  end

As we have seen, in order to generate an array of points of, for example, a sinusoid, we can invoke this functionn in the following way:

function_points(xy(0, 0), sinusoid, 0.75, 0.5, 0, 0, 15, 0.4)

Note that, because function_points was defined as a generalization sinusoid_points and parabola_points, it introduced the f function as a parameter and, in addition, generalized the a, omega and phi parameters of the function sinusoid_points and xv, yv and d of the function parabola_points, calling them, respectively, alpha, beta and gamma. It so happens that these parameters never change during the recursive calls performed by the function_points function. In fact, the function passes these parameters only because they are necessary for the invocation of the f function. Let us now imagine that, instead of passing these parameters, they were associated to the f function itself. In this case, we could rewrite the function_points function in the following form:

function_points(p, f, x0, x1, dx) =

  if x0 > x1

    []

  else

    [p+vxy(x0, f(x0)), function_points(p, f, x0+dx, x1, dx)...]

  end

With this new definition, the previous call would have to be rewritten as:

function_points(xy(0, 0), x -> sinusoid(0.75, 0.5, 0, x), 0, 15, 0.4)

Rewriting the function function_points has a considerable advantage: it allows the use of any base functions, regardless of the number of parameters it has: if they have only one parameter, they can be used directly, otherwise we can wrap them with an anonymous function of one parameter that invokes the desired function with all the required arguments. This makes the function function_points even more generic, as it is visible in following examples, which create the curves shown in this figure.

> spline(function_points(xy(0, 6), sin, 0, 4*pi, 0.2))

Spline(...)

> spline(function_points(xy(0, 3), x -> -(x/10)^3, 0, 4*pi, 0.5))

Spline(...)

> spline(function_points(xy(0, 0),

                         x -> damped_oscillation(1.5, 0.4, 4, x),

                         0,

                         4*pi,

                         0.05))

Spline(...)

Curve generated by the function function_points; from top to bottom: a sinusoid, an inverted exponential, and a damped sinusoid.

(show-tikz 1.2)

Even though the generic character of higher-order functions allows these to be used in a wide variety of situations, sometimes, it is preferable to use a more easily recognizable function for a more particular use. For example, if a program is systematically computing points of a sinusoid, it is likely to become more readable if, in fact, the sinusoid_points function exists. But even in this case, the higher-order functions will allow this function to be defined in a simpler way. For instance, instead of having to write the usual recursive definition:

sinusoid_points(p, a, omega, phi, x0, x1, dx) =

  if x0 > x1

    []

  else

    [p+vxy(x0, sinusoid(a, omega, phi, x0)),

     sinusoid_points(p, a, omega, phi, x0+dx, x1, dx)...]

  end

we can now write:

sinusoid_points(p, a, omega, phi, x0, x1, dx) =

  function_points(p, x -> sinusoid(a, omega, phi, x), x0, x1, dx)

9.3.1 Exercises 42
9.3.1.1 Question 161

Consider the balconies shown on the following image:

image

The balconies are composed of a slab and a balustrade, being the balustrade composed of balusters and a handrail. Define a function called balcony that, if conveniently parametrized, is capable of generating not only the balconies shown in the previous image but also many others. For that, the balcony function should receive not only the geometric parameters of the balcony (such as the slab’s thickness and the handrail’s height), but also the function which determines the outside curve of the balcony.

Define the functions that reproduce the balconies shown in the previous image.

9.3.1.2 Question 162

The following image presents a random walk composed of cylindrical tubes parallel the coordinate axes, and joined by spheres. The tubes have a random length between a maximum length and \(10\%\) of that maximum length; note that the tubes’ directions are also random but never the reverse of the immediately previous direction. The tubes have a radius that is \(2\%\) of its maximum length and the spheres have a radius that is \(4\%\) of the maximum length of the tube.

image

Define the function tubes_walk that, given the initial position and direction, the maximum length of tube and the number of tubes, creates a sequence of tubes joined by spheres following a random walk.

9.3.1.3 Question 163

Even though the walk can be random, it is possible to limit it in space so as to never exceed a certain region. This limitation is visible in the following image where, from left to right, we can see a sequence of tubes following a random walk limited by a cube, a cylinder, and a sphere.

image image image

Define the function tubes_shape as a generalization of the function tubes_walk in order to receive, in addition to the parameters of the latter, an additional parameter that should be a predicate that, given a hypothetical position to the walk extension, indicates whether this position is contained in the region in question. If it is not, the program should reject this position and generate a new one.

Also define the functions tubes_cube, tubes_cylinder and tubes_sphere, which have the same parameters of the function tube_walk and that use the function tubes_shape with the appropriate predicate to the desired shape.