Package

org.taffy.core.maths

Object Hierarchy
What is it?

A Function is just like a mathmeatical function — it maps one or more input variables to arithmetic. It can be used in mathematical expressions, evaluated, integrated and derived. It can cache its results, and is atomic.

Quick Example
f(x) = x^2



Table of Contents:

Constructing a Function

Left-side Function Declaration

function_name ( variable1, variable2, … ) = arithmetic

Right-side Function Declaration

function_name = [Function createWithBlock: { < variable1, variable2, … > arithmetic } ]

A function maps one or more input variables to arithmetic. A Function is created using left-side or right-side construction. left-side construction is similar to how one writes a mathematical function definition by hand. The input variable names are arbitrary, but for our examples we use x, y and z. The following are examples of various left-side constructed functions:

f(x) = x^2
g(x, y) = sin(x) * cos(y)
h(x, y, z) = x^(e^cos(y)) * tan(z)

The following uses right-side construction to create the same functions as above:

f = [Function createWithBlock: { <x> x^2 }]
g = [Function createWithBlock: { <x, y> sin(x) * cos(y) }]
h = [Function createWithBlock: { <x, y, z> x^(e^cos(y)) * tan(z) }]

A function may be defined with one or more variables. The following are examples of one-, two- and three-dimensional functions:

f(x) = x
f(x, y) = x + y
f(x, y, z) = x * y * z^2

Evaluating a Function

A function is evaluated using the () operator:

f(x) = x^2
f(2)
==> 4

f(x, y) = x^y
f(2, 3)
==> 8

If function memory is enabled (see Memory), the result of the evaluation will be cached for future lookup.

Arithmetic and Functions

Functions can be used in arithmetic. Functions greedily consume the arithmetic around them, on both left and right sides. For example,

// define the function, f
f(x) = 3x
==> #F(x) = 3x

// multiply f by 3, on the left, it creates a new Function
3 * f
==> #F(x) = 3 * 3 * x

// divide 3 by f, it creates a new Function
3 / f
==> #F(x) = 3 / (3x)

// multiply f by 3, on the right, it creates a new Function
f * 3
==> #F(x) = 3 * x * 3

Function Simplification

The method simplify attempts to simplify a function using algebra and trigonometry identities.

f(x) = 3 / (3x)
f simplify
==> #F(x) = 1 / x

f(x) = 1 - 1
f simplify
==> #F(x) = 0

f(x) = sin(x)^2 + cos(x)^2
f simplify
==> #F(x) = 1

Compilation

The method compile attempts to resolve all non-input symbols referenced in the Function’s arithmetic. The compile method may throw an UndefinedObjectException if the Function references an undefined object.

a = 5

f(x) = x + a
f compile
==> #F(x) = x + 5

g(x) = x + b
g compile
==> Uncaught Exception: UnidentifiedObjectException: "unidentified object: b"
    ...

Composition

Functions compose other functions by wrapping them in the () operator:

f(x) = x^2
g(x) = x^3

// define h as (f ∘ g)
h(x) = f(g(x))

h compile
==> #F(x) = (x^3)^2

Integration and Derivation

The integrate: method performs integration, and the derive: method performs derivation. Each takes a single argument, a Symbol representing the value to be integrated or differentiated against, or the actor. integrate: and derive: have default versions that treat the variable x as the actor. Examples:

// define the function f
f(x) = x^2

// integrate f by x
f integrate: 'x
==> #F(x) = 2 * x^3

// the "integrate" method gives the same result as "integrate: 'x"

// derivate f by x
f derive: 'x
==> #F(x) = 2x

// the "derive" method gives the same result as "derive: 'x"

// derive f by y
f derive: 'y
==> #F(x) = 0

Function Scope

A Function’s evaluation scope is the same as the scope that the function itself resides in. This means that variables defined on the same scope level as the Function are accessible within it.

value = 5
f(x) = x + value
f(2)
==> 7

Base Cases

A Function uses a base case to fix its output for a given input. Base cases are defined with the following syntax:

Base Case Syntax

function_name ( boolean-expression ) = value
function_name ( number ) = value

A well-known function that use base cases is the Fibonacci function. The Fibonacci function is defined as f(n) = f(n - 1) + f(n - 2), with the base cases f(0) = 0 and f(1) = 1.

// define the Fibonacci function
f(n) = f(n - 1) + f(n - 2)

// define the base cases
f(0) = 0
f(1) = 1

// alternatively, the base cases can be written as: f(n <= 1) = n

// the function is not defined for input less than 0
f(n < 0) = NaN

// evaluate the function
f(10)
==> 55

The following defines a function that is 1 for all input less than 0, and -1 for all input greater than 0. At 0, the function is equal to 0.

f(x) = 0
f(x < 0) = 1
f(x > 0) = -1

Memory

By default a Function caches the last three values it computed. This cache can save processing time when a Function is called multiple times with the same input. We refer to the cache as the Function’s memory.

The memory size for all Functions is set via the method Function#setDefaultMemorySize:, and it is queried via the method Function#getDefaultMemorySize. Note that setting a new default memory size only affects Functions that are created afterward.

A Function’s memory size is changed via the method setMemorySize:, and it is queried via the method getMemorySize:. Setting the memory size to 0 disables the cache.

// set the default memory size to 5
Function setDefaultMemorySize: 5

f(x) = x^2
f getMemorySize
==> 5

// set the default memory size to 30
Function setDefaultMemorySize: 30

g(x) = x^3
g getMemorySize
==> 30

// f's memory size is still 5
f getMemorySize
==> 5

// g can set its own memory size, but this will not affect f
g setMemorySize: 0
g getMemorySize
==> 0

f getMemorySize
==> 5

Caveats

A newly-constructed right-side constructed function may be passed as an argument to a method call, while left-side constructed functions must be created and resolved before being used as a method argument. The following examples illustrate this:

// valid, pass the anonymous function #F(x) = x^2
[foo executeWithFunction: [Function createWithBlock: { <x> x^2 }]

// valid
f(x) = x^2
// f is now resolved and can be used as a method argument
[foo executeWithFunction: f]

// invalid, f must be resolved before being used as a method argument
[foo executeWithFunction: f(x) = x^2]