```
using MTH229
using Plots
plotly()
```

`Plots.PlotlyBackend()`

A notebook for this material: ipynb

We see in this section how to easily create functions in `Julia`

. In the following sections we begin to do things with function, such as learning how to graph functions with `Julia`

.

For basic things creating a new function and plotting it is as familiar as this:

We begin by loading some packages:^{1}

```
using MTH229
using Plots
plotly()
```

`Plots.PlotlyBackend()`

Then we define a function and plot it over \([0, \pi]\):

```
f(x) = sin(3x^2 - 2x^3)
plot(f, 0, pi)
```

Really, you’d be hard pressed to make this any shorter or more familiar. Of course, not everything is this easy so there are still things to learn, but keep in mind that 90% of what we want to do in these projects is really this straightforward.

Mathematically, a function can be viewed in many different ways. An abstract means is to think of a function as a mapping, assigning to each \(x\) value in the function’s domain, a corresponding \(y\) value in the function’s range. With computer languages, such as `Julia`

, the same holds, though there may be more than one argument to the function and with `Julia`

the number of arguments and type of each argument are consulted to see exactly which function is to be called.

Here we don’t work abstractly though. For a mathematical function (real-valued function of a single variable, \(f: \mathbb{R} \rightarrow \mathbb{R}\)), we typically just have some rule for what the function will do to \(x\) to produce \(y\), such as

\[ f(x) = \sin(x) - \cos(x). \]

In `Julia`

there are a few different ways to define a function, we start with the most natural one which makes it very simple to work with such functions.

Real-valued functions (\(f: \mathbb{R} \rightarrow \mathbb{R}\)) are often described in terms of elementary types of functions such as polynomial, trigonometric, or exponential. We give examples of each in the following.

Of course `Julia`

has readily available all the usual built-in functions found on a scientific calculator, and many more. See the section on mathematical operations and functions of the official `Julia`

documentation. In the following, we show how to translate some basic math functions into `Julia`

functions:

\[ f(x) = \cos(x) - \sin^2(x) \]

becomes

`f(x) = cos(x) - sin(x)^2`

`f (generic function with 1 method)`

About exponents and functions…

The conversion from the commonly written form (\(\sin^2(x)\)) to the far less ambiguous \(\sin(x)^2\) is very important. This is necessary with `Julia`

– as it is with calculators – as there is no function `sin^2`

. In Julia, squaring is done on values – not functions, like `sin`

. (And most likely squaring of a function is more likely to be composition, which is not the usage here.) So, to have success, learn to drop the notations \(\sin^2(x)\) or for the arc sine function \(\sin^{-1}(x)\). These shortcuts are best left in the age when mathematics was done just on paper.

Degrees, not radians

If you want to work in degrees you can do so with the degree-based trigonometric functions, which follow the same naming pattern save a trailing “d”:

`fd(x) = cosd(x) - sind(x)^2`

`fd (generic function with 1 method)`

This is not used in the following, rather, when needed converting from degrees to radians through multiplication by `pi/180`

is. (There is also `deg2rad`

.)

A mathematical definition like

\[ f(x) = 2\tan^{-1}\left(\frac{\sqrt{1 - x^2}}{1 + x}\right) \]

becomes

`f(x) = 2atan( sqrt(1-x^2) / (1 + x) )`

`f (generic function with 1 method)`

This particular function is just an alternative expression for the arc cosine (mathematically \(\cos^{-1}\) but in `Julia`

`acos`

) using the arctan function, as seen here:

`f(.5) - acos(.5) ## nearly 0`

`-2.220446049250313e-16`

The exponent in the inverse trigonometric functions is *just* mathematical notation for the longer expression “arctan” or “arccos”. (It definitely is not a reciprocal.) The `Julia`

functions – like most all computer languages – abbreviate these names to `atan`

, `acos`

or `asin`

.

The math function

\[ f(x) = e^{-\frac{1}{2}x^2} \]

Can be expressed as

`f(x) = e^(-(1/2)*x^2)`

`f (generic function with 1 method)`

The value of \(e\) is built-in to `Julia`

, but not immediately available. It is s exposed by the `MTH229`

package. But \(e\) can be inadvertently redefined. As such, it is a safer practice to use the `exp`

function, as in:

`f(x) = exp(-(1/2)*x^2)`

`f (generic function with 1 method)`

There isn’t much difference in use, but don’t try to do both at once, as in `exp^(-(1/2)*x^2)`

!

The mathematical notations for logarithms often include \(\ln\) and \(\log\) for natural log and log base 10. With computers, there is typically just `log`

for natural log, or with an extra argument the logarithm to other bases.

\[ f(x) = \ln(1 - x) \]

becomes just

`f(x) = log(1 - x)`

`f (generic function with 1 method)`

Whereas, the base 10 log:

\[ f(x) = \log_{10}(1 + x) \]

can be done through:

`f(x) = log(10, 1 + x)`

`f (generic function with 1 method)`

where the *first* argument expresses the base. For convenience, `Julia`

also gives the functions `log10`

and `log2`

for base \(10\) and \(2\) respectively.

In mathematics a typical observation is to recognize some object as a combination of simpler objects. For functions, we think of combining simpler functions into more complicated ones. For example, we can think of the sum of functions, \(h(x) = f(x) + g(x)\). The rule for each \(x\) is simply to *add* the results of the two rules for \(f\) and \(g\) applied to \(x\). Notationally, we might write this as either:

\[ h = f + g \]

or

\[ h(x) = f(x) + g(x). \]

The former treats \(f\) and \(g\) as function objects, the latter ties more closely to the concept of a function as a rule that operates on \(x\).

With `Julia`

the latter representation is more useful for defining combinations of functions. For example, if \(f(x) = \sin(x)\) and \(g(x) = x^2\), then we can combine these in several ways. The following illustrates several ways to combine the two functions \(f\) and \(g\):

```
f(x) = sin(x)
g(x) = x^2
h(x) = f(x) + g(x) # f + g
h(x) = f(x) - g(x) # f - g
h(x) = f(x) * g(x) # f * g
h(x) = f(x) / g(x) # f / g
h(x) = f(x)^g(x) # f^g
```

`h (generic function with 1 method)`

All these are based on underlying mathematical operators. In addition, for functions there is the operation of *composition*, where the output of one function is the input to another. For example:

```
h(x) = f(g(x)) # f ∘ g or sin(x^2)
h(x) = g(f(x)) # g ∘ f or (sin(x))^2
```

`h (generic function with 1 method)`

This operation is fundamentally non-commutative, as the above example illustrates.

Which of these functions will compute \(\sin^3(x^2)\)?

Which of these functions will compute

\[ \frac{1}{\sqrt{2\pi}} e^{-\frac{1}{2}x^2}? \]

Define the function \(f(x) = -16x^2 + 100\).

Is \(f(4)\) positive?

Define the function \(f(x) = x^3 - 3x + 2\)

What is the value of \(f(10)\)?

Define the function \(f(x) = x^5 + x^4 + x^3\)

What is the value of \(f(2)\)?

Which of these functions will compute \(f(x) = x^2 -2x + 1\)?

Which of these functions will compute

\[ f(x) = \frac{x^2 - 2x}{x^2 - 3x}? \]

Which of these functions will compute

\[ f(x) = e^{-x} \sin(x)? \]

If you want to define a more complicated function, say one with a few steps to compute, an alternate form for defining a function can be used:

```
function function_name(function_arguments)
...function_body...
end
```

The last value computed is returned unless the `function_body`

contains a `return`

call.

For example, the following is a more verbose way to define \(f(x) = x^2\):

```
function f(x)
return(x^2)
end
```

`f (generic function with 1 method)`

The line `return(x^2)`

, could have just been `x^2`

as it is the last (and) only line evaluated.

Imagine we have a complicated function, such as:

\[ g(x) = \tan(\theta) x + \frac{32}{200 \cos\theta} x - 32 \log\left(\frac{200 \cos\theta}{200\cos\theta - x}\right). \]

where \(k\) is the constant 1/2 and \(\theta=\pi/4\). To avoid errors in transcribing, it can be be useful to break such definitions up into steps. Here we note the repeated use of \(200\cos(\theta)\) in the definition of \(g(x)\), so we give that value the intermediate name of `a`

```
function g(x)
= pi/4
theta = 200*cos(theta)
a tan(theta)*x + (32/a)*x - 32*log(a/(a-x))
end
```

`g (generic function with 1 method)`

From this, we can easily see that we would need to be concerned as \(x\) approaches the value of `a`

, as when \(x \geq a\) the logarithm won’t be defined.

Here is a different example, where we define a “hockey stick” function, a name for functions that are flat then increase linearly after some threshold.

An old-school cell-phone plan might cost $30 for the first 500 minutes of calling and 25 cents per minute thereafter. Represent this as a function of the number of minutes used.

Here we need to do one of two things depending if \(x\) is greater or less than \(500\). There are different ways to do this, here we use an `if-else-end`

statement, which takes the following form:

```
function cell_phone(x)
if x < 500
return(30.0)
else
return(30.0 + 0.25*(x-500))
end
end
```

`cell_phone (generic function with 1 method)`

To see what it would cost to talk for 720 minutes in a month, we have:

`cell_phone(720)`

`85.0`

A subtlety

We return `30.0`

above – and not the integer `30`

– when \(x<500\) so that the function always returns a floating point value and not an integer if less than 0 and a floating point value if bigger. In general it is a good programming practice to have functions return only one type of variable for a given type of input. In this case, as the answer could be real-valued – and not just integer-valued, we want to return floating point values.

A quick plot will show why the above function is called a “hockey stick” function:

`plot(cell_phone, 0, 1000)`

When functions that have different rules based on the specific value of \(x\) that is input, the use of “cases” notation is common. For example,

\[ f(x) = \begin{cases} \cos(x) & x \geq 0\\ 1 - e^{-1/x^2} & \text{otherwise}. \end{cases} \]

Translating this notation to `Julia`

can also be done with the `if-else-end`

construct:

```
function f(x)
if x >= 0
cos(x)
else
1 - exp(-1/x^2)
end
end
```

`f (generic function with 1 method)`

The expression after `if`

is a *Boolean value* (a `true`

or `false`

value). In these examples they are generated through the *Boolean operators*, which include the familiar comparison symbols `<`

, `<=`

, `==`

, `>=`

, and `>`

. (Only `==`

takes learning, as double equal signs are used for comparison, a single one is for assignment.)

One can use the so-called *ternary operator* `a ? b : c`

for simple `if-else-end`

statements as above.

Basically, `a ? b : c`

is the same as the more verbose

```
if a
b
else
c
end
```

So the cell-phone example could have been a one-liner:

`cell_phone(x) = x < 500 ? 30.0 : 30.0 + 0.25*(x - 500)`

`cell_phone (generic function with 1 method)`

When `x < 500`

the expression right after `?`

is evaluated, and if not, the expression after `:`

is.

For mathematical functions, the directness of the ternary operator usually makes it a preferred choice over `if-else-end`

.

It can be convenient to nest ternary operators. In particular, when the cases involve have more than 2 possibilities. The following does something depending on whether `x`

is positive, negative or zero:

`heaviside(x) = x > 0 ? 1.0 : x == 0.0 ? 0.0 : -1.0`

`heaviside (generic function with 1 method)`

That is a mess to read, but easy to write. It can be made a bit clearer by using parentheses around the case where `x`

is not greater than 0:

`heaviside(x) = x > 0 ? 1.0 : (x == 0.0 ? 0.0 : -1.0)`

`heaviside (generic function with 1 method)`

Similarly, new lines can clear up the flow:

```
heaviside(x) = x > 0 ? 1.0 :
== 0.0 ? 0.0 :
x -1.0
```

`heaviside (generic function with 1 method)`

Which of these definitions will be the equivalent of \(f(x) = |x|\)? (The `abs`

function is already one):

The `sign`

function returns \(-1\) for negative numbers \(1\) for positive numbers and \(0\) for 0. Which of these functions could do the same?

T-Mobile has a pay as you go cell phone plan with the following terms:

- You pay 30 per month and this includes the first 1500 minutes or text messages combined.
- Each additional minute or message costs 13 cents.

Which of these functions will model this?

A alternate mathematical notation for a function that emphasizes the fact that \(f\) maps \(x\) to some value \(y\) involving the rule of \(f\) is to use an arrow as:

\[ x \rightarrow -16x^2 + 32x \]

You can do the exact thing in `Julia`

to create a function:

`-> -16x^2 + 32x x `

`#11 (generic function with 1 method)`

This expression creates a function object, but since we didn’t bind it to a variable (that is, we didn’t give the function a name) it was immediately forgotten. Such functions without a name are known as *anonymous functions*.

Anonymous functions can be named. For example, we might have:

`= x -> -16x^2 + 32x motion_of_particle `

`#13 (generic function with 1 method)`

This names a function object; it does not create a method for a generic function (to be described later). One source of possible confusion is the name given to an anonymous function *can not* be used as a name for a function defined through the style `f(x) = ...`

, as that creates a method for a *generic function*. Vice versa.

The concept of a function is of much more general use than its restriction to mathematical functions of single real variable. A natural application comes from describing basic properties of geometric objects. The following function definitions likely will cause no great concern when skimmed over:

```
Area(w, h) = w * h # of a rectangle
Volume(r, h) = pi * r^2 * h # of a cylinder
SurfaceArea(r, h) = pi * r * (r + sqrt(h^2 + r^2)) # of a right circular cone
```

`SurfaceArea (generic function with 1 method)`

The right-hand sides may or may not be familiar, but it should be reasonable to believe that if push came to shove, they could be looked up. However, the left-hand sides are subtly different – they have two arguments, not one. In `Julia`

it is trivial to define functions with multiple arguments – we just did. However, `Julia`

has two means to specify additional arguments, as described briefly in the following.

Functions like `Area`

above use *position* to match the values used when calling the function with the variables in the function definition. This is quite natural. We would expect `Area(5,6)`

to use `w=5`

and `h=6`

when the evaluation is performed.

The function call `log(x)`

finds \(\log_e(x) = \ln(x)\); the function call `log2(x)`

finds \(\log_2(x)\); the function call `log10(x)`

finds \(\log_{10}(x)\). More generally we have `log(a, b)`

to find the log for a specified base.

Does `log(a, b)`

find \(\log_a(b)\) *or* \(\log_b(a)\)?

In a function definition, there can be \(0, 1\), or more positional arguments, even an unspecified variable number of arguments. The values may be restricted to having different types. Default values may be given. Here we stick to the simplest cases, as illustrated above, where the variables are all named and separated by commas in the function definition.

Positional arguments work well for many cases, but are not always the most covenient approach:

if there are numerous arguments, the user must know what position matches to what variable which can mean consulting the documentation or some other means. For example, in

`Volume`

above is the radius the first or second argument? It is hard to guess without peeking.if there are numerous arguments but the user only wants to change a few from the

*default*values the user would need to still use all the arguments. (Well, not technically, but practically.)

The latter issue is well solved with *keyword arguments*.

Keyword arguments are widely used when plotting with `Julia`

as they conveniently help customize specific attributes of a plot on a case-by-case basis. The `Plots`

package we will illustrate utilizes *positional arguments* for the data to be plotted and *keyword arguments* to adjust attributes of how the data is shown.

For an example using keyword arguments, we revisit this function

\[ g(x) = \tan(\theta) x + \frac{32}{200 \cos\theta} x - 32 \log\left(\frac{200 \cos\theta}{200\cos\theta - x}\right). \]

The value \(\theta\) may have a default value of \(\pi/4\) for most uses, but rather than hard code that value, we set it as a default and allow it to be adjusted by the user by passing the argument `theta=...`

.

Keyword arguments are separated from any positional arguments by a *semicolon* and are given a *default value*:

For example:

```
function g(x; theta=pi/4)
= 200*cos(theta)
a tan(theta)*x + (32/a)*x - 32*log(a/(a-x))
end
```

`g (generic function with 1 method)`

If keyword arguments are not specified when called, the defaults are used:

`g(50)`

`47.35323911536457`

Is the same as `g(50, theta=pi/4)`

.

To modify a keyword argument it is specified as `key=value`

**after** any positional arguments are specified in the function call.

For example, if the angle were less than the default of \(\pi/4\) would the value of \(f\) be smaller or larger?

`g(50, theta=pi/8) ## smaller in this case.`

`19.272845247997708`

The pattern `g(theta=pi/8, 50)`

would error.

We used a comma to separate the positional argument from the keyword argument. Commas are used to separate different positional arguments; commas are used to separate different keyword arguments, so this is natural. However, a semicolon can also be used, to mirror their mandated use when *defining* a function with keyword arguments.

Earlier we saw the `log`

function can use a second argument to express the base. This function is defined by `log(b, x) = log(x) / log(b)`

. The `log(x)`

value is the natural log, and this definition just uses the change-of-base formula for logarithms.

But not so fast, on the left side is a function with two arguments and on the right side the functions have one argument – yet they share the same name. How does `Julia`

know which to use? `Julia`

uses the number, order, and *type* of the *positional* arguments passed to a function to determine which *method* for the generic function to use.

This is technically known as **multiple dispatch** or **polymorphism**. As a feature of the language, it can be used to greatly simplify the number of functions the user must learn. The basic idea is that many functions are “generic” in that they will work for many different scenarios.

A good example is addition. In experience, different algorithms are used for adding integers (using carrying); adding decimal numbers (align the decimal points); adding complex numbers (add real and imaginary parts separately); adding polynomials (add coefficients of like terms); etc. Each definition may be different, but the concept the same. For the end user, only the operator `+`

need be used. A similar thing occurs with `Julia`

.

Some upcoming example usage of generic functions will include:

`find_zero(f, c)`

and`find_zero(f, a, b)`

will both find a zero of`f`

, but the first form will look for one*near*`c`

; the second for a zero between`a`

and`b`

– while using a*totally*different algorithm.`plot(f, a, b)`

will plot the function`f`

over the interval`[a,b]`

. However,`plot(xs, ys)`

will plot the points \((x_1, y_1), (x_2, y_2), \dots (x_n, y_n)\) specified with two contains.

For each example, the same general idea is being asked (finding a zero or rendering a plot) but different implementations, or methods, are eventually used.

Anonymous and generic functions

`Julia`

has two types of functions: anonymous functions and generic functions. (There are also “callable” structs which act like functions.) Anonymous functions, despite their name, can be named in the way a name can be given to a value, just like naming any other value.

Generic functions are more complicated. In order to have one name and many methods, a method table is used behind the scenes.

Generic function names in the method table (e.g., `plot`

, `log`

, etc.) can not be repurposed as variable names or vice versa. `Julia`

is a dynamic language otherwise, where the type of values attached to a variable name can be changed, but not in this usage.

Hence the following will error, as `h1`

is used a variable and then a function:

`= 4 h1 `

`4`

`h1(x) = 4`

`LoadError: cannot define function h1; it already has a value`

Similarly, this will error when the attempt to redefine `h2`

is made:

`h2(x) = 4`

`h2 (generic function with 1 method)`

`= 4 h2 `

`LoadError: invalid redefinition of constant Main.h2`

The suggestion is to use familiar names for variables (e.g., `x`

, `y`

, `a`

, …) and either descriptive function names or the usual common names (e.g. `f`

, `g`

, …) for *generic* functions (those defined by `f(x) = ...`

.)

`Julia`

’s multiple dispatch allows multiple functions with the same name. The function that gets selected depends on the arguments given to the function. We can exploit this to simplify our tasks. For example, consider this optimization problem:

For all rectangles of perimeter \(20\), what is the one with largest area?

The start of this problem is to represent the area in terms of one variable. We see next that composition can simplify this task, which when done by hand requires a certain amount of algebra.

Representing the area of a rectangle in terms of two variables is easy:

`Area(w, h) = w * h`

`Area (generic function with 1 method)`

But the other fact about this problem – the constraint that the perimeter is \(20\) – means that height depends on width. For this question, we can see that \(P=2w + 2h\) so that

`h(w) = (20 - 2 * w)/2`

`h (generic function with 1 method)`

By hand we would substitute this last expression into that for the area and simplify (to get \(A=w\cdot (20-2 \cdot w)/2 = -w^2 + 10\)). However, within `Julia`

we can let composition do the substitution and leave algebraic simplification for `Julia`

to do:

`Area(w) = Area(w, h(w))`

`Area (generic function with 2 methods)`

This might seem odd, as now we have two *different* but related functions named `Area`

. Julia will decide which to use based on the number of arguments when the function is called. This allows both to be used on the same line, as above. This usage is not common with computer languages, but is a feature of `Julia`

which is built around the concept of *generic* functions with multiple dispatch rules to decide which rule to call.

For example, the `plot`

function, when called as below, expects functions of a single numeric variable. Behind the scenes, then the function `A(w)`

will be used in this graph:

`plot(Area, 0, 10)`

From this, we can see that that the width yielding the maximum area is \(w=5\), and so \(h=5\) as well.

Many descriptions involve both a variable (or variables) and different parameters. In calculus many functions are part of wider family of functions (exponential, logaorithmic, etc.) that use parameters to fit the family to a problem at hand. Function transformations are an example.

A even more familiar example is the equation for a line \(y = m\cdot x + b,\) where \(x\) is a variable and \(m\) and \(b\) are **parameters** for a given application.

The equation written as a function might be implemented through:

`f(x) = m * x + b`

`f (generic function with 1 method)`

The value of `x`

is *passed* into the function when `f`

is called, but where would the values for `m`

and `b`

be found? In math, these parameters would come from the *context* of the problem being discussed. On the computer, there are *scoping rules* used to resolve the task of identifying a value assigned to a variable in a given scope.

Were `f`

defined as above, the values of `m`

and `b`

would likely come from the *global scope*. This uses the *current* values assigned to the symbols `m`

and `b`

and **not** the values when `f`

is defined. This means if those values are changed, the values `f`

computes will be changed.

For the most part this is just fine, but it is fragile and can lead to subtle mistakes.

In general, using global variables is frowned upon for the reason above and for performance reasons. Rather is it suggested that parameters are passed in the functions that use them. We illustrate three possible means to specify parameters when calling a function in the following example.

The line is an essential object in calculus and other subjects. Their ubiquity leads to different mathematical descriptions:

- The general
*equation*of a line: \(Ax + By = C\) - the slope-intercept
*equation*of a line \(y = m\cdot x + b\), as above - the point-slope
*equation*of a line \(y = y_1 + m \cdot (x - x_1)\)

The general equation is useful, as it can describe vertical lines, where the slope is undefined. However, the use of the slope-intercept or point-slope forms is more commonly employed in calculus, especially the point-slope form as the data describing a line is usually presented in terms of these two quantities.

Both of these latter forms can be written in function form, e.g. \(f(x) = m\cdot x + b\).

Consider the task mentioned above of making a function for the slope-intercept form of a line, \(f(x) = m\cdot x + b\), where \(x\) is the variable; \(m\) and \(b\) the parameters.

Some different forms that can be used:

- Using different positional arguments for the variable and the parameters:

`three_arguments(x, m, b) = m * x + b`

`three_arguments (generic function with 1 method)`

- Using the
`f(x, p)`

form, with two positional arguments, and where the parameters are passed through in a container:

`fxp(x, p) = p[1] * x + p[2] # p = (m, b) is passed`

`fxp (generic function with 1 method)`

- Using keyword arguments for the parameters:

`keyword_arguments(x; m=0, b=1) = m * x + b`

`keyword_arguments (generic function with 1 method)`

For the point-slope form of a line, these three styles might become:

```
four_arguments(x, m, x1, y1) = y1 + m * (x - x1)
fxp(x, p) = p[3] + p[1] * (x - p[2]) # p = (m, x1, y1) is passed
keyword_arguments(x; m=0, x1 = 0, y1 = 0) = y1 + m * (x - x1)
```

`keyword_arguments (generic function with 1 method)`

All three forms have their merits:

The multiple arguments form is the easiest to understand. The style is used in most calculus books to describe multi-variable functions.

The

`f(x, p)`

style is an extremely common design pattern in the`Julia`

ecosystem. It provides a consistent interface for scalar values and multivariate values just by passing in containers of values for`x`

and`p`

. (We would pass in a container of`(m,b)`

or`(m, x1, y1)`

to use the above; more sophisticated containers and unpacking schemes are used in practice. Named tuples in particular are convenient.)The keyword arguments form is utilized in many statistics book, as most probability distributions are parameterized. This style is very explicit for the user; the default values and calling style allows for the easiest customization from the defaults. Keyword arguments do not participate in method dispatch.

In computer science terminology, `Julia`

is a language which treats functions as *first class objects*. In the `plot`

function, illustrated above, the basic syntax for the call is `plot(f, a, b)`

, with the function object, `f`

, being passed as an argument to `plot`

.

This particular usage fits into a more general template: `verb(function_object, arguments....)`

that will be seen in other contexts later in these notes.

In calculus an *operator* is some operation that takes a function and produces a different, but related function. Calculus has two main operators: the derivative and, in some scenarios, the integral, as will be discussed elsewhere.

In `Julia`

it is natural to use functions which mirror mathematical operators: functions which accept other functions as inputs and output function objects. We call such functions operators here, though that term isn’t so standard. In the example below, we give an illustration. However, the main example – the derivative – will wait until later.

One difference we highlight is that since operators return functions we simply *assign* them names such as `f = ...`

, when that is desirable, rather than use the function definition notation of `f(x) = ...`

. Though we are mindful that the same name can’t be used for both styles, as the former style assigns a variable to a function object, the latter creates a method for a function name.

Returning to our discussion of a line, suppose we have the function using multiple arguments to represent parameters:

`point_slope(x, m, x1, y1) = y1 + m * (x - x1)`

`point_slope (generic function with 1 method)`

In calculus, a *secant line* is related to a function, \(f(x)\), and *two* points \((a,f(a))\) and \((b, f(b)).\) As two points determine a line, this is well defined. Suppose `f`

, `a`

, and `b`

are specified, say by:

```
f(x) = x^2
= 0, 1 a, b
```

`(0, 1)`

then we can create the secant line for this data as:

```
= (f(b) - f(a)) / (b-a)
m = a, f(a) # or b, f(b) it doesn't matter
x1, y1 secant_line(x) = point_slope(x, m, x1, y1)
```

`secant_line (generic function with 1 method)`

This takes the values of `f`

, `a`

, and `b`

(or the derived *parameters* `m`

, `x1`

, and `y1`

) and creates a function of just `x`

. This function can be called like any other function:

`secant_line(3)`

`3.0`

While the above construction is easy to understand, it is also a bit of a fragile construction, as the values of the parameters, `m`

, `x1`

, and `y1`

are *global* variables derived from the basic inputs `f`

, `a`

, and `b`

. As mentioned earlier, when `secant_line`

is called the current values assigned to the global variables are used – not necessarily the same values as when `secant_line`

is defined.

To be explicit about the values of the parameters being *fixed*, a different approach is typically used.

Below, we write a function to return an *anonymous function* with the values for the parameters fixed. In computer science language, we are creating a *closure*:

```
function Secant(f, a, b)
= (f(b) - f(a)) / (b -a)
m = a, f(a)
x1, y1 -> point_slope(x, m, x1, y1)
x end
```

`Secant (generic function with 1 method)`

The same “`secant_line`

” function would be generated through:

`= Secant(f, a, b) sl `

`#18 (generic function with 1 method)`

To see they are the same, let’s just call them at an arbitrary value of `x`

and compare:

`sl(3), secant_line(3)`

`(3.0, 3.0)`

`sl`

” – not “`sl(x)`

”The `Secant`

function above constructs and returns a *function*, so `sl`

is a function object. We don’t need to write `sl(x)`

on the *left-hand side*, as we do when we define a generic function or method through an expression of its arguments. This *can* be done, were the following usage employed:

`sec_line(x) = Secant(f, a, b)(x)`

`sec_line (generic function with 1 method)`

but this is not recommended: it is a bit awkward to type, is inefficient, and—*most importantly*—uses *global* values for `a`

and `b`

each time it is called.

The `secant(f, a, b)`

function in the `MTH229`

package returns a function like `Secant`

that has a reminder of what it does when used:

```
f(x) = x^2
= 0, 1
a, b = secant(f, a, b) sl
```

```
Function of `x` to compute the secant line of `f` between `a` and `b`:
f(a) + ((f(b)-f(a)) / (b-a) * (x-a)
```

The `sl`

object is a black-box function, but it can be used to identify the slope and the intercept of the secant line being represented.

For example, the intercept of a line (the \(b\) in \(y = m\cdot x + b\)) is the value of the function when \(x=0\) or:

`= sl(0) b `

`0.0`

The slope can be found by taking *any* two points, forming \((x_0, y_0)\) and \((x_1, y_1)\) and using the slope formula:

```
= 2, 3
a, b = a, sl(a)
x0, y0 = b, sl(b)
x1, y1 = (y1 - y0) / (x1 - x0) m
```

`1.0`

Or more directly, the two intermediate lines can be skipped:

`= (sl(b) - sl(a)) / (b - a) m `

`1.0`

The package defines a related function `tangent(f, c)`

to return a *function* which computes the tangent line to `f`

at `c`

:

```
f(x) = x^2
= 1
c = tangent(f, c) tl
```

```
Function of `x` to compute the tangent line of `f` at `c`:
f(c) + f'(c) * (x-c)
```

For the line described by `tl`

, compute:

- the \(y\)-intercept:

- the slope:

- the \(x\)-intercept:

Partial function application

A partial function application is related to the closure above. Consider our first example for a function representing the slope-intercept form using three arguments:

`slope_intercept(x, m, b) = m * x + b`

`slope_intercept (generic function with 1 method)`

For a fixed `m`

and `b`

, we can use the following style to create a function of `x`

as follows:

`SlopeIntercept(m, b) = x -> slope_intercept(x, m, b)`

`SlopeIntercept (generic function with 1 method)`

When the function *returned* by `SlopeIntercept`

is called, it uses the fixed values for `m`

and `b`

.

The pattern of fixing a value of a function is common enough that there are also functions `Base.Fix1`

and `Base.Fix2`

. Both are for functions of *two* variables, `f(x,y)`

. The first is used to create a function of `y`

for a *fixed* `x`

; the second to create a function of `x`

for a fixed `y`

. While the style of `SlopeIntercept`

above, where an anonymous function is returned, is natural, the implementation of these two “fix” functions is more performant and their use appears often behind the scenes.

Which of these function definitions corresponds to shifting the function `f`

to the right by `c`

units and up by `d`

units with a default of \(0\) and \(0\):

Which of these definitions will lengthen the period of a periodic function \(f\) by a factor of \(c\), with a default of \(1\)?

The following transform of a function is at the core of wavelet theory:

`g(t; a=1, b=0) = (1/sqrt(a)) * f((t - b)/a)`

`g (generic function with 1 method)`

If \(f(x) = \sin(x)/x\) and \(a=2\) and \(b=1\) compute \(g(0, a=2, b=1)\).

Let \(g\) be defined by:

```
function g(x; theta=pi/4)
= 200*cos(theta)
a tan(theta)*x + (32/a)*x + 32*log((a-x)/a)
end
```

`g (generic function with 1 method)`

For `x`

in 20, 25, 30, 35, 40, 45 degrees, what value will maximize `g(125, theta=x*pi/180)`

?

What anonymous function will compute \(\sin(x^2)\)?

What anonymous function of \(x\) will return the polynomial \(x^2 - 2x\):

What does this function do?

```
function mystery(f)
-> -f(x)
x end
```

`mystery (generic function with 1 method)`

What does this operator do?

```
function trim(f, c)
-> abs(f(x)) <= c ? f(x) : NaN
x end
```

`trim (generic function with 1 method)`

This section presents some additional details on writing functions in `Julia`

that are here for informational purposes only.

As mentioned, the value returned by a function is either the last value executed or any value returned by `return`

. For a typical real valued function \(f\) this is usually just a number. Sometimes it is convenient to return more than one value. For this a *tuple* proves useful:

A *tuple* is a container for holding different objects at once. They are made quite simply by enclosing the values in parentheses:

`1, "one") (`

`(1, "one")`

Tuples have many uses, but here we want to focus on their use as return values. Here is a somewhat contrived example. Imagine you write a function to compute the value of \(f(x) = x^x\), but you want to ensure \(x\) is positive, as otherwise there will be an error. You can do this, where we return a value of `NaN`

and a message when the user tries to use a negative number:

`f(x) = x > 0 ? (x^x, "") : (NaN, "You can't use non-positive numbers")`

`f (generic function with 1 method)`

We include a message even when the value of \(x\) is okay, as it is good practice –though not a requirement of `Julia`

– to always return the same type of object, regardless the input.

A simple call would be:

`f(-1)`

`(NaN, "You can't use non-positive numbers")`

We get a tuple back. `Julia`

makes working with tuple return values very easy. We can *destructure* them by simply placing two variable names on the left-hand side:

`= f(-1) # alternatively: (a, b) = f(-1) a, msg `

`(NaN, "You can't use non-positive numbers")`

A less artificial example will be discussed later: the `quadgk`

function which estimates the value of an integral. For this computation both the value and an estimated maximum error are of interest, so both are returned using a tuple.

Typical functions here are real-valued functions of a single variable. The easiest way to use these is to just mimic the regular mathematical notation as much as possible. However, there are times where we want to be specific about what possible values a user can place into a function. For example, a naive function to compute the binomial coefficients,

\[ { n \choose k } = \frac{n!}{(n-k)! k!}, \]

can be specialized to just integer values with:

`binom(n::Integer, k::Integer) = factorial(n)/(factorial(n-k) * factorial(k))`

`binom (generic function with 1 method)`

The extra bit `::Integer`

is called a *type annotation* and specializes `n`

and `k`

so that this function only is called with both `n`

and `k`

are of this type.

As defined, we can call our `binom`

function as:

`binom(10, 4)`

`210.0`

But not as follows (\(\pi\) is not an integer):

`binom(10, pi)`

MethodError: no method matching binom(::Int64, ::Irrational{:π}) Closest candidates are: binom(::Integer, ::Integer) @ Main In[100]:1

(The actual `binomial`

function is much better than this, as it doesn’t divide a big number by a big number, which can cause real issues with loss of precision, though it does specialize to integers, and any sub-type. It also always returns an integer, whereas ours returns a floating-point value.)

Types in `Julia`

are a more complicated matter than we want to get into here, but we do want to list the common types useful for basic calculus: `Function`

, `Real`

, `Integer`

, `Rational`

, `Complex`

, and `Number`

(real or complex).

Clearly the latter ones should nest, in that an object of type `Integer`

should also be of type `Real`

. This means when we specialize a mathematical function, it is enough to specify values of `Real`

.

The `MTH229`

package loads a small package, `SimpleExpressions`

, which can be used to blur the distinction between an expression and a function. Later on, the `SymPy`

package will be illustrated which allows this blurring and *much* more.

Consider the polynomial \(p = 100 + 20x - 16x^2\). Polynomials can be viewed either as a mathematical expression consisting of indeterminates or as a polynomial function.

As an expression of indeterminates, polynomials can be algebraically combined, eg. added, multiplied, etc. They can be factored; in calculus integrated and differentiated.

As functions they can be evaluated; used as examples of continuous and differentiable functions; integrated and differentiated, etc.

Within `Julia`

the distinction is important:

*expressions*are immediately evaluated- functions have their evaluation delayed until they are called

Unlike expressions, function’s can’t be added or subtracted without the effort of creating a new function, as previously illustrated.

To illustrate, without first setting a value for `x`

, the expression below would error:

```
= 2
x = 100 + 20x - 16x^2 p
```

`76`

As `2`

is assigned to `x`

, the value `2`

is substituted in for the symbol `x`

and the expression is immediately evaluated and assigned to `p`

.

Contrast this with the definition as a polynomial function:

`f(x) = 100 + 20x - 16x^2`

`f (generic function with 1 method)`

The variable `x`

need not be defined when `f`

is defined, a value for the variable is assigned when the function is called. For example:

`f(2)`

`76`

The `SimpleExpressions`

package allows the creation of one symbolic variable and optionally one symbolic parameter. These can be used in an expression as above. However, rather than being evaluated immediately in the expression, the evaluation steps involving the variable are queued up and deferred until a value is assigned.

The `@symbolic`

macro is used to create the variable, which is then used within other expressions:^{2}

```
@symbolic x
= 100 + 20x - 16x^2 p
```

`(100 + (20 * x)) - (16 * (x ^ 2))`

This symbolic expression can be added and multiplied, as is typically done by hand:

`= (1 + 2p)*p^2 q `

`(1 + (2 * ((100 + (20 * x)) - (16 * (x ^ 2))))) * (((100 + (20 * x)) - (16 * (x ^ 2))) ^ 2)`

A new expression is returned and assigned to `q`

above. These symbolic expressions are not simplified.^{3}

The expressions in `SimpleExpressions`

are also functions^{4} and can be used where functions would be.

For example, we can easily call them:

`p(2)`

`76`

or

`q(3)`

`8448`

Importantly, these expressions can be passed in as functions to other functions. Skipping ahead a bit, we see that a simple expression can be used in place of a function when plotting:

`plot(x^5 - x - 1, -1, 1.5) # Plots is loaded`

One final feature of simple expressions is they can also represent equations. This is done by separating the left and right hand sides with a `~`

.

A plot recipe is defined, so plotting of these equations is straightforward and highlights the two sides as separate functions:

```
= sin(x^2) ~ x/2
eqn
plot(eqn, 0, pi)
```

Again, skipping ahead, we see the three intersection points in \([0,\pi]\) can be identified with:^{5}

`solve(eqn, 0, pi)`

```
3-element Vector{Float64}:
0.0
0.505482272339305
1.511542718555805
```

Finally, to show that having a parameter might be useful, what if the intersection points in terms of the slope of the line (\(1/2\) above) was of interest? In the following we parameterize it and show how to substitute in a value for the parameter:

```
@symbolic x p
= sin(x^2) ~ x/p
eqn1 solve(eqn1(:, 3), 0, pi)
```

```
5-element Vector{Float64}:
0.0
0.3340259282913851
1.6052940219459464
2.7243212874268083
2.857225258605647
```

As can be imagined, the number of intersection points depends on how flat the line is. This shows for `p=2`

three intersection points, but for `p=3`

five such.

The `@symbolic`

macro treats the first symbol as a symbolic variable, the second (when given) as a symbolic parameter. The notation `eqn(:, 3)`

substitutes `3`

in for the symbolic parameter `p`

, returning an equation in `x`

that can be passed to the zero-finding function.

Environment of

`Julia`

when generated
`Julia`

version:

`VERSION`

`v"1.10.4"`

Packages and versions:

```
using Pkg
Pkg.status()
```

```
Status `~/work/mth229.github.io/mth229.github.io/Project.toml`
[a2e0e22d] CalculusWithJulia v0.2.6
[7073ff75] IJulia v1.25.0
[b964fa9f] LaTeXStrings v1.3.1
[ebaf19f5] MTH229 v0.3.2
[91a5bcdd] Plots v1.40.4
[f27b6e38] Polynomials v4.0.11
[438e738f] PyCall v1.96.4
[612c44de] QuizQuestions v0.3.23
[295af30f] Revise v3.5.14
[f2b01f46] Roots v2.1.5
[24249f21] SymPy v2.1.1
[56ddb016] Logging
```

Packages are a means to extend base

`Julia`

. There are thousands of add-on packages for`Julia`

, but these notes basically use just the two here, which rely on a handful of other add-on packages.↩︎The

`SymPy`

package will be used later on in these notes. It too has symbolic variables, but those variables have much more flexibility and the`SymPy`

package has many, many more features.`SimpleExpressions`

sole purpose is very modest, just to make basic expressions of a single variable and optional parameter act like functions.↩︎Later, when

`SymPy`

is used for symbolic computations, some basic simplification happens automatically.↩︎The type holding a simple expression subtypes

`Function`

and has a “call” method defined to allow the expressions to have the same calling style as a function.↩︎The

`solve`

generic finds solutions to a problem. It is widely used within the`Julia`

ecosystem, but not so much in these notes. The call`solve(f::Function, ...)`

has too many different possible interpretations to privilege just one; to call a desired one, the function is typically wrapped into a distinguishing type. So, the use of`solve`

here would be`solve(ZeroProblem(f,...),...)`

which gets a bit verbose for this level, where a different name will suffice, as seen later. However, in this example, the`eqn`

expression has a specific type, so`eqn`

need not be wrapped into another special type.↩︎