6.11 Piece-Wise Function Definitions

To move beyond functions defined in one line, we introduce in this section functions that are defined piece-by-piece. That is, we say use this definition when the argument is such-and-such and use this other definition when the argument is that-and-that.

6.11.1 A Basic Example

There are many other ways to define a factorial function for nonnegative integers. You might function:piece-wise definition say piece-wise function definition factorial of 0 is 1, otherwise factorial of n is n times factorial of n-1. Here is one way to do this in FriCAS.

Here is the value for n=0.

fact(0) == 1

Type: Void

Here is the value for n>0. The vertical bar | means such that. such that

fact(n | n > 0) == n * fact(n - 1)

Type: Void

What is the value for n=3?

fact(3)
Compiling function fact with type Integer -> Integer
Compiling function fact as a recurrence relation.
\[\]
6

Type: PositiveInteger

What is the value for n=-3?

fact(-3)
You did not define fact for argument -3 .

Now for a second definition. Here is the value for n=0.

facto(0) == 1

Type: Void

Give an error message if n<0.

facto(n | n < 0) == error "arguments to facto must be non-negative"

Type: Void

Here is the value otherwise.

facto(n) == n * facto(n - 1)

Type: Void

What is the value for n=7?

facto(3)
Compiling function facto with type Integer -> Integer
\[\]
6

Type: PositiveInteger

What is the value for n=-7?

facto(-7)
Error signalled from user code in function facto:
   arguments to facto must be non-negative

Type: PositiveInteger

To see the current piece-wise definition of a function, use )display value.

)display value facto
Definition:
  facto 0 == 1
  facto (n | n < 0) ==
    error(arguments to facto must be non-negative)
  facto n == n facto(n - 1)

In general a piece-wise definition of a function consists of two or more parts. Each part gives a piece of the entire definition. FriCAS collects the pieces of a function as you enter them. When you ask for a value of the function, it then glues the pieces together to form a function.

The two piece-wise definitions for the factorial function are examples of recursive functions, that is, functions that are defined in terms of themselves. Here is an interesting doubly-recursive function. This function returns the value 11 for all positive integer arguments.

Here is the first of two pieces.

eleven(n | n < 1) == n + 11

Type: Void

And the general case.

eleven(m) == eleven(eleven(m - 12))

Type: Void

Compute elevens, the infinite stream of values of eleven.

elevens := [eleven(i) for i in 0..]
\[\]
[11,11,11,11,11,11,11,11,11,11,…]

Type: Stream Integer

What is the value at n=200?

elevens 200
\[\]
11

Type: PositiveInteger

What is the FriCAS’s definition of eleven?

)display value eleven
Definition:
  eleven (m | m < 1) == m + 11
  eleven m == eleven(eleven(m - 12))

6.11.2 Picking Up the Pieces

Here are the details about how FriCAS creates a function from its pieces. FriCAS converts the i-th piece of a function definition into a conditional expression of the form: if predi then expressioni. If any new piece has a predi that is identical (after all variables are uniformly named) to an earlier predj, the earlier piece is removed. Otherwise, the new piece is always added at the end.

If there are n pieces to a function definition for f, the function

defined f is: | if pred1 then expression1 else | . . . | if predn then expressionn else | error “You did not define f for argument <arg>.”

You can give definitions of any number of mutually recursive function definitions, piece-wise or otherwise. No computation is done until you ask for a value. When you do ask for a value, all the relevant definitions are gathered, analyzed, and translated into separate functions and compiled.

Let’s recall the definition of eleven from the previous section.

eleven(n | n < 1) == n + 11

Type: Void

eleven(m) == eleven(eleven(m - 12))

Type: Void

A similar doubly-recursive function below produces -11 for all negative positive integers. If you haven’t worked out why or how eleven works, the structure of this definition gives a clue.

This definition we write as a block.

minusEleven(n) ==
  n >= 0 => n - 11
  minusEleven (5 + minusEleven(n + 7))

Type: Void

Define s(n) to be the sum of plus and minus eleven functions divided by n. Since 11-11=0, we define s(0) to be 1.

s(0) == 1

Type: Void

And the general term.

s(n) == (eleven(n) + minusEleven(n))/n

Type: Void

What are the first ten values of s?

[s(n) for n in 0..]
\[\]
[1,1,1,1,1,1,1,1,1,1,…]

Type: Stream Fraction Integer

FriCAS can create infinite streams in the positive direction (for example, for index values 0,1,…) or negative direction (for example, for 0,-1,-2,…). Here we would like a stream of values of s(n) that is infinite in both directions. The function t(n) below returns the n-th term of the infinite stream [s(0),s(1),s(-1),s(2),s(-2),…] Its definition has three pieces.

Define the initial term.

t(1) == s(0)

Type: Void

The even numbered terms are the s(i) for positive i. We use quo rather than / since we want the result to be an integer.

t(n | even?(n)) == s(n quo 2)

Type: Void

Finally, the odd numbered terms are the s(i) for negative i. In piece-wise definitions, you can use different variables to define different pieces. FriCAS will not get confused.

t(p) == s(- p quo 2)

Type: Void

Look at the definition of t. In the first piece, the variable n was used; in the second piece, p. FriCAS always uses your last variable to display your definitions back to you.

)display value t
Definition:
  t 1 == s(0)
  t (p | even?(p)) == s(p quo 2)
  t p == s(- p quo 2)

Create a series of values of s applied to alternating positive and negative arguments.

[t(i) for i in 1..]
Compiling function s with type Integer -> Fraction Integer
Compiling function t with type PositiveInteger -> Fraction Integer
\[\]
[1,1,1,1,1,1,1,1,1,1,…]

Type: Stream Fraction Integer

Evidently t(n)=1 for all i. Check it at n=100.

t(100)
\[\]
1

Type: Fraction Integer

6.11.3 Predicates

We have already seen some examples of function:predicate predicates predicate:in function definition (ugUserPieceBasic ). Predicates are Boolean-valued expressions and FriCAS uses them for filtering collections (see ugLangIts ) and for placing constraints on function arguments. In this section we discuss their latter usage.

The simplest use of a predicate is one you don’t see at all.

opposite 'right == 'left

Type: Void

Here is a longer way to give the opposite definition.

opposite (x | x = 'left) == 'right

Type: Void

Try it out.

for x in ['right,'left,'inbetween] repeat output opposite x
Compiling function opposite with type
   OrderedVariableList [right, left,inbetween] -> Symbol
 left
 right

The function opposite is not defined for the given argument(s).

Explicit predicates tell FriCAS that the given function definition piece is to be applied if the predicate evaluates to true for the arguments to the function. You can use such constant arguments for integers, function:constant argument strings, and quoted symbols. constant function argument The Boolean values true and false can also be used if qualified with @ or `` $`` and Boolean. The following are all valid function definition fragments using constant arguments.

a(1) == ...
b("unramified") == ...
c('untested) == ...
d(true@Boolean) == ...

If a function has more than one argument, each argument can have its own predicate. However, if a predicate involves two or more arguments, it must be given after all the arguments mentioned in the predicate have been given. You are always safe to give a single predicate at the end of the argument list.

A function involving predicates on two arguments.

inFirstHalfQuadrant(x | x > 0,y | y < x) == true

Type: Void

This is incorrect as it gives a predicate on y before the argument y is given.

inFirstHalfQuadrant(x | x > 0 and y < x,y) == true
1 old definition(s) deleted for function or rule inFirstHalfQuadrant

Type: Void

It is always correct to write the predicate at the end.

inFirstHalfQuadrant(x,y | x > 0 and y < x) == true
1 old definition(s) deleted for function or rule inFirstHalfQuadrant

Type: Void

Here is the rest of the definition.

inFirstHalfQuadrant(x,y) == false

Type: Void

Try it out.

[inFirstHalfQuadrant(i,3) for i in 1..5]
Compiling function inFirstHalfQuadrant with type (PositiveInteger,
   PositiveInteger) -> Boolean
\[\]
[false,false,false,true,true]

Type: List Boolean