Functions in Symata

In [1]:
using Symata # load Symata and enter Symata mode

There are (more or less) three ways to define functions in Symata.

  • via rules
  • via the host language (Julia)
  • via the pure function expression head Function

Basics of defining and using functions

This is how you define a function using rules

In [2]:
f(x_) := x^2


$$ 4 $$

Note the trailing underscore in x_. This function is implemented via a rule for rewriting expressions.

The argument to f can be any expression:

In [3]:
[f(x+y), f("dog"), f(2.0)]

$$ \left[ \left( x + y \right) ^{2},\text{"dog"}^{2},4.0 \right] $$

In [4]:
ClearAll(f)   # remove the definition for `f`

This computes the nth Fibonacci number.

In [5]:
fib(1) := 1
fib(2) := 1
fib(n_) := fib(n-1) + fib(n-2)


$$ 55 $$

A compound statement can be written like this.

In [6]:
addone(x_) := (a = 1,  x + a)


$$ 1 + y $$

But, the variable a is not local to the function. The global value of a is set.

In [7]:

$$ 1 $$

To make variables local to a function, use Module

In [8]:
g(x_) := Module([c,d],(c=1,d=3,a+d+x))    # local variables are lexically scoped.

In [9]:

$$ 4 + z $$

The global values of c and d are not affected.

In [10]:

$$ \left[ c,d \right] $$

The argument to the function f in In(2) can be any expression. You can also make multiple definitions that apply only when the argument satisfies some requirements. For example, the following makes a definition for g that only applies for floating point arguments greater than 5.

In [11]:
gt5(x_) := x > 5

g(x_Float:?(gt5)) := 1

In [12]:
g(10.0)   # The conditions in the new definition are satisfied by 10.0

$$ 1 $$

In [13]:
[g(4.0), g(10), g(q + r)] # The definition in In(13) applies here

$$ \left[ 8.0,14,4 + q + r \right] $$

You can do structural matching on the argument. This matches anything that has the form of a square.

In [14]:
h(x_^2) := x

In [15]:
[h(r^2), h(r^3), h((r+q)^2), h(Expand((r+q)^2)), 4 , 2]

$$ \left[ r,h \! \left( r^{3} \right) ,q + r,h \! \left( q^{2} + 2 \ q \ r + r^{2} \right) ,4,2 \right] $$

Although 4 is the square of 2, 2 is the square of Sqrt(2), and q^2 +2*q*r + r^2 is the square of r+q, none of these match. Only expressions of the form Power(expr,2) match. We see why h(r^2) matches by looking at the full form of r^2.

In [16]:


In [17]:

Use two trailing underscores to match one or more elements

In [18]:
g(x_Symbol, p__Integer) := Apply(Plus, x^[p])

In [19]:

$$ y + y^{4} + y^{7} + y^{10} $$

In [20]:
Apply(ClearAll, UserSyms())

Set and SetDelayed

Why did we use := rather than = above ? One reason is that it is safer. The following example shows why.

In [21]:
f(x_) := x^2
g(x_) = x^2
x = 3
h(x_) = x^2
j(x_) := x^2

[f(a), g(a), h(a), j(a)]

$$ \left[ a^{2},a^{2},9,a^{2} \right] $$

In the definition of h, x is not a dummy variable. If you are beginning with Symata it is enough to know that := should be preferred and you can skip the following.

What does f(x_) := x^2 do ? When Symata evaluates this expression, it does not evaluate the right hand side, but rather holds it in its unevaluated form. When the rule is applied, say by evaluating f(a+b), the value that matches x_, in this case a+b, is substituted for x in the right hand side and the result is evaluated with the result (a+b)^2. Note that the value of x at the time the definition is first evaluated has no affect on this result: The right hand side is never evaluated before subsituting for x.

The definition g(x_) = x^2 differs in that the right hand side is evaluated when the definition is made. After this, everything is the same as for :=. The value that matches x_ is substituted in the right hand side and the result is evaluated. If x was unbound when the definition was made, the results are identical, because the substituion will be made before evaluating the right hand side again, so assignments to x in the mean time will have no effect. However if x is bound at the time of the definition, then the value of x^2 at the time of defnition is stored and the match of x_ is substituted into this expression. If x had a value of 3, then a+b is substituted into 3, which just results in 3.

These "function" definitions actually define DownValues. We can see that the definitions of f,g, and j are the same, no matter what the future values of x.

In [22]:
Map(DownValues, [f,g,h,j])

$$ \left[ \left[ \text{HoldPattern} \! \left( f \! \left( x\text{_} \right) \right) \text{:>}x^{2} \right] , \left[ \text{HoldPattern} \! \left( g \! \left( x\text{_} \right) \right) \text{:>}x^{2} \right] , \left[ \text{HoldPattern} \! \left( h \! \left( x\text{_} \right) \right) \text{:>}9 \right] , \left[ \text{HoldPattern} \! \left( j \! \left( x\text{_} \right) \right) \text{:>}x^{2} \right] \right] $$

In [23]:
ClearAll(f,g,h,j,x)  # clear the symbols used in this example

Julia functions

We mentioned above that these defintions define Rules. You can also use compiled functions in Symata. One way is to code such a function directly in the host language, Julia.

In [24]:

In [25]:
#  J(expr) interprets expr as Julia code
f = J((x) -> x^2 ) ;

In [26]:

$$ 9 $$

In [27]:
f("dog")  # This calls the Julia function, so we get what "dog"^2 means in Julia

$$ \text{"dogdog"} $$

Note that we did not use := in the definition of f. That is, f := :((x) -> x^2 ). This would create a new anonymous function everytime f is called, which would be very slow.

In [28]:

$$ \left( a + b \right) ^{2} $$

The last example works because ^ has been overloaded in Julia to operate on Symata expressions. (Symata expressions are objects of type Symata.Mxpr in Julia.)

Pure (anonymous) functions

Anonymous or pure functions are used like this:

In [29]:
Function(x, x^2)(a)

$$ a^{2} $$

The infix operator -> is equivalent to Function

In [30]:
(x -> x^2)(a)

$$ a^{2} $$

A function of two variables is defined like this:

In [31]:
([x,y] -> x^2 + y^2)(3,4)

$$ 25 $$

In [ ]: