Creating New Functions Using Functional and Rule-Based Programming

George E. Hrabovsky

MAST

Introduction

Last time we explored how to program in the traditional procedural style. This time we will explore two paradigms that form the heart of how to use MMA/WL. The first is functional programming, programming through the use of operations on functions. The second is rule-based programming where we develop programs that take advantage of the pattern-matching and transformation systems built into MMA.

For functional programming, after a brief introduction we will explore what are called pure functions. Then we will explore how to apply functions to an existing list. Then we will discuss how to perform iterative operations. We will end our discussion of functional programming with how to compose functions and how to apply a function in operator form.

For rule-based programming, after a brief introduction, we discuss how MMA/WL handles patterns. We will discuss pattern matching. We will end with a discussion of rules.

What is Functional Programming?

Functional programming has its foundations in the development of what is called the lambda calculus founded by Alonzo Church and extended by Alan Turing, Moses Schönfinkel, and Haskell Curry in the 1920s and 1930s. In the late 1950’s John McCarthy of MIT developed the first language that used functional programming, LISP. What is a functional programming language? There is no simple definition, rather we can see what the characteristics are.

If a language allows you to pass functions as the argument for other functions, then we say that the language allows for first-class functions, another way of saying this is that functions are first-class citizens of the language.

Related to this is the existence of functions that take other functions as arguments, or they return functions as results. Such functions are called higher-order functions.

There is a kind of function whose values are the same for the same arguments. If such functions have no side-effects on the program then the function is called a pure function. This property of functions is called referential transparency.

If a function can call itself from within the function then it is called a recursive function. This replaces the tradition looping structure of the procedural program.

Functional Programming in MMA/WL

It is axiomatic that everything in MMA/WL is an expression. Many expressions are built around functions. The standard notation for functions is Head[argument(s)]. Head is that name of the function. All functions built-in to MMA/WL start with upper-case Latin letters.

If you have made an assignment it applies to the Header of a function.

l3_1.png

l3_2.png

In this sense, the Header itself can be thought of as an expression.

You can make a function that defines a function by using the Header as an argument.

l3_3.png

l3_4.png

l3_5.png

It is this ability to treat function names as expressions that open MMA/WL to the panoply of characteristics of functional programming.

Pure Functions

There are two equivalent ways of writing pure functions in MMA/WL. The first is by writing out the Header Function.

l3_6.png

l3_7.png

We then apply this to a variable.

l3_8.png

l3_9.png

We can do the same thing using the equivalent short form.

l3_10.png

l3_11.png

l3_12.png

l3_13.png

We can apply this to functions of several arguments by numbering the short-form arguments. When using the short form we must always close the short form with the & to tell MMA/WL to stop at that point.

l3_14.png

l3_15.png

l3_16.png

l3_17.png

If we have a collection of operations we want to perform by combining functions of several arguments we use short-form for a list of all arguments.

l3_18.png

l3_19.png

l3_20.png

l3_21.png

l3_22.png

l3_23.png

If we have a list of arguments and we want to ignore the first one, we use the short form of the list of arguments except the first n, ##n, where n tells MMA/WL where to start the list of arguments.

l3_24.png

l3_25.png

l3_26.png

l3_27.png

Applying Functions to Lists

If we want to apply a function to a list we use Apply.

l3_28.png

l3_29.png

We can also use the short form.

l3_30.png

l3_31.png

Here is an example

l3_32.png

l3_33.png

If we want to apply a function to every element in a list we use the Map command.

l3_34.png

l3_35.png

We can also use the short form the we introduced in lesson 2.

l3_36.png

l3_37.png

For a more concrete example.

l3_38.png

l3_39.png

We can also use this to apply a function to another list of functions.

l3_40.png

l3_41.png

l3_42.png

l3_43.png

Say we have a matrix.

l3_44.png

l3_45.png

We can apply Map to the individual elements of the matrix.

l3_46.png

l3_47.png

There is another type of command that applies the function to every level of the matrix.

l3_48.png

l3_49.png

There are three levels of the matrix to which the function can be applied to: the whole matrix, each row, and then each element of the row. We can use the short form of this, too.

l3_50.png

l3_51.png

We can get a list of the levels and their positions.

l3_52.png

l3_53.png

We can see this in the row of the matrix, too.

l3_54.png

l3_55.png

We can also apply this to all levels.

l3_56.png

l3_57.png

We can map a function to a part of an expression or list. Here we map a function to the third part of a list.

l3_58.png

l3_59.png

Here we have a new matrix.

l3_60.png

l3_61.png

The first row is part 1, the second part 2, both rows have three parts in their own right. Here we map a function to the first part of the first row, and the second part of the second row.

l3_62.png

l3_63.png

While Map produces a new expression, we might want to apply a function of a list without creating a new expression. To  do this we can use the Scan command.

l3_64.png

l3_65.png

l3_66.png

l3_67.png

We can also apply a function to parts of an expression.

l3_68.png

l3_69.png

l3_70.png

l3_71.png

l3_72.png

Iterations with Functions

As stated earlier in functional programming we do not use looping structures, we use recursion. In MMA/WL the Nest command applies a function recursively a number of times on an expression. We write Nest[function,expression,number of recursive steps]. We can make up an abstract example.

l3_73.png

l3_74.png

For example we can see how to recursively apply a square root to the number 100 five times.

l3_75.png

l3_76.png

We can write this as a pure function.

l3_77.png

l3_78.png

If we want to see each intermediate value of the recursion process we use the NestList command.

l3_79.png

l3_80.png

For the example we developed, we get this result.

l3_81.png

l3_82.png

Sometimes we want to recursively work with a function until a result no longer changes. We do this with the FixedPoint function. We write FixedPoint[function, starting point]. This example comes from the Tutorial Functional Operations included in the documentation. The goal will be to apply Newton’s approximation of the square root of three, we begin with a single step.

l3_83.png

We choose a starting point of x=1.

l3_84.png

l3_85.png

We can see how many recursive steps are required by using the FixedPointList command.

l3_86.png

l3_87.png

We can also apply a function recursively until a test fails. We write NestWhile[function,starting value,test], here is an example. We devise a function to divide a number by some divisor, say 2.

l3_88.png

l3_89.png

l3_90.png

We can see the intermediate values.

l3_91.png

l3_92.png

Say we want to apply a function recursively for a fixed argument, and a second argument is from a list.

l3_93.png

l3_94.png

We can see each recursive step.

l3_95.png

l3_96.png

Another Notation for Functions

There is an important way of writing some functions. For example, here we take the derivative of the function f with respect to the variable x.

l3_97.png

l3_98.png

This is the wrong answer, we need to write the function correctly.

l3_99.png

l3_100.png

Another way to write this is with the derivative operator.

l3_101.png

l3_102.png

Or we can use a new way of writing it. Command [function head] [variable]. For example we can write the derivative.

l3_103.png

l3_104.png

Note that this last allows for abstract function place holders to be used. We can do this without a variable.

l3_105.png

l3_106.png

So this form of function can be very useful. It is not implemented for every function. Check the documentation to see if it is available for the function you want to use.

Functional Composition and Operators

In set theory, and thus all of mathematics—using set theory as its foundational language—there is an operation where one function is allowed to operate on another function. This is called a composition. If the function f is allowed to operate on the function g(x), the composition is written fg or f(g(x)). Here is how we compose functions in MMA/WL.

l3_107.png

l3_108.png

We can also use the short form.

l3_109.png

l3_110.png

We can reverse the direction of the composition.

l3_111.png

l3_112.png

Here is its short form.

l3_113.png

l3_114.png

We can compose the inverse functions.

l3_115.png

l3_116.png

Using the Identity command returns the suitable identity result.

l3_117.png

l3_118.png

Through performs the same task as apply, except for the Heads.

l3_119.png

l3_120.png

This example is taken from the documentation. If we define a differential operator.

l3_121.png

l3_122.png

Now we use Through to apply the operator to a sine function.

l3_123.png

l3_124.png

Here we construct a table of natural logarithms

l3_125.png

0. Indeterminate
1. 0.
2. 0.693147
3. 1.09861
4. 1.38629
5. 1.60944
6. 1.79176
7. 1.94591
8. 2.07944
9. 2.19722
10. 2.30259

If we want a function to operate on another function we can write Operate[function a,function b[arguments]].

l3_126.png

l3_127.png

Here is an example.

l3_128.png

l3_129.png

Operate defaults to the level 1 specification, this is the head. We can choose to specify the level.

l3_130.png

l3_131.png

We can apply Operate recursively.

l3_132.png

l3_133.png

The process of transforming a collection of sublists into a single list is called flattening the list. We write Flatten[list]. Here we have a complicated list of sublists, taken from the documentation.

l3_134.png

l3_135.png

l3_136.png

l3_137.png

Here we flatten only at level 1.

l3_138.png

l3_139.png

We can use Flatten to work with heads

l3_140.png

l3_141.png

We can choose which Head to flatten.

l3_142.png

l3_143.png

l3_144.png

l3_145.png

Sometimes we want to flatten elements at only specific locations in a list. We use FlattenAt[list,element number].

l3_146.png

l3_147.png

What is Rule-Based Programming?

Rule-based programming is based on formal logic. We establish a set of rules, based on patterns and pattern matching. The rules transform the results in ways that we choose. The programs are built up out of the transformation rules.

The history of rue-based programming extends back to the 1960s and is a bit murky. The first really successful language came out in 1972 when Alan Colmerauer collaborated with Philippe Roussel (using prior work done by Robert Kowalski) in Marseilles, France. This resulted in a language called Prolog.

Patterns

When we want MMA/WL to know that something is a pattern we want it to remember for the line of code being made, we use a blank. Pattern_ tells MMA/WL that Pattern needs to be looked at. In function a pattern in the argument is applied to the body of the function. Here is a function to find the prime. We only want integers as input.

l3_148.png

l3_149.png

l3_150.png

The short form x_ is the same as another form x:_.

If we want MMA/WL to look for a pattern with one or more elements we use the double underscore. This represents a BlankSequence. The label represents the name of the sequence in the body of pattern.

l3_151.png

l3_152.png

l3_153.png

l3_154.png

l3_155.png

l3_156.png

Here, again, x__ is the same as x:__.

If we want MMA/WL to look for a pattern with zero or more elements we use the triple underscore. We call this a BlankNullSequence

l3_157.png

l3_158.png

l3_159.png

When we want to specify different options for patterns as use a vertical line, |, to separate them, this is the short form of the command Alternatives.

l3_160.png

l3_161.png

l3_162.png

l3_163.png

l3_164.png

l3_165.png

l3_166.png

l3_167.png

l3_168.png

l3_169.png

If we have a sequence of arguments, but we don’t care what order we input them we can use OrderlessPatternSequence[pattern list]. This example is taken from the documentation.

l3_170.png

l3_171.png

l3_172.png

l3_173.png

l3_174.png

l3_175.png

l3_176.png

We can do this for all argument of a function by using the SetAttributes command and the Orderless command. This example is taken from the documentation.

l3_177.png

l3_178.png

l3_179.png

There are times when we want a default argument in a function. Default[function head] is the command to use. In this state the function is written with an argument pattern function[arg_.] where _. is a pattern that can be ignored.

l3_180.png

l3_181.png

l3_182.png

l3_183.png

l3_184.png

l3_185.png

l3_186.png

l3_187.png

If you want to look up all of the attributes of a command or symbol use the Attributes[symbol].

l3_188.png

l3_189.png

Pattern Matching

If we have a list, and we want to get a sublist of all elements that match a pattern we use Cases[list,pattern]. For example, if we want to know what elements of a list that are Integers, we might have something like this.

l3_190.png

l3_191.png

If we want to know where a matched element is in an expression, we use Position[expression,pattern].

l3_192.png

l3_193.png

l3_194.png

l3_195.png

If we want to know how many elements of a list match a pattern we use Count[list,pattern]. This example is taken from the documentation.

l3_196.png

l3_197.png

We can also apply this to associations.

l3_198.png

l3_199.png

Note that this only counted those cases that matched the pattern in level 1.

l3_200.png

l3_201.png

If we want to remove all elements from a list that matches a pattern we use DeleteCases[list,pattern]. These examples are taken from the documentation.

l3_202.png

l3_203.png

Or in operator form.

l3_204.png

l3_205.png

If we have a pattern matching problem and we want the expression unevaluated during it, we use the HoldPattern[expression] command.

l3_206.png

l3_207.png

l3_208.png

l3_209.png

Rules

A Rule is a statement where lhs is transformed into the rhs by the relation of an arrow lhs→rhs. We can make this a replacement rule by ending an expression with the ReplaceAll symbol /. as we see here.

l3_210.png

l3_211.png

If we want a different result each time a replacement rule is evaluated we use the RuleDelayed symbol. This is :> . Here is an example from the documentation.

l3_212.png

l3_213.png

If we want to perform a transformation recursively until the result no longer changes we use ReplaceRepeated, //., here is an example from the documentation. MMA/WL does not apply the properties of the logarithm.

l3_214.png

Here is what we get when we use the ReplaceAll command.

l3_215.png

l3_216.png

Here is what happens when we use ReplaceRepeated.

l3_217.png

l3_218.png

If we need to use an input exactly in a rule or pattern we use the Verbatim[expression] command. This example is taken from the documentation. We begin with a delayed rule to take the square of an expression.

l3_219.png

We can apply this to an expression

l3_220.png

l3_221.png

If we want to make this work only for specific number types we can write.

l3_222.png

l3_223.png

l3_224.png

l3_225.png

Values assigned directly to a function are called downvalues. We can see a list of these by using the DownValue[function head] command.

l3_226.png

l3_227.png

l3_228.png

We can use the language here to write our own downvalues.

l3_229.png

l3_230.png

We can use the Definition command to see how a symbol is defined.

l3_231.png

h[1]:=1
h[y_]:=2[h[y-1]]

l3_232.png

l3_233.png

Values assigned directly to composed functions are called upvalues. We can use UpValues[function head] to find these rules. Here is an example from the documentation.

l3_234.png

l3_235.png

l3_236.png

l3_237.png

Created with the Wolfram Language