Skip to content

Functions as Data

Functions as data

Functions in Morpho are objects just like Lists, Strings, etc. Hence, they can be assigned to variables, and called at a later point

var P = sin
print P(Pi/10)

Functions can also be stored in collections. This example computes the value of several trigonometric functions, which are stored in a list:

var lst = [sin, cos, tan]
for (f in lst) print f(Pi/3)

Finally, functions can be passed as arguments to other functions. For example, here's a function that applies a given function twice to its second argument

fn calltwice(f, x) {
  return f(f(x))
}

You can then use calltwice with any function, as in this example:

fn sqr(x) {
  return x^2
}

print calltwice(sqr, 2)

Anonymous functions

Morpho provides an abbreviated syntax for functions that can be used in assignments or as parameters. There's no need to give the function a namesuch functions are hence called anonymousand you may, optionally, provide a single statement as the body in place of the usual code block. The value of the body statement is returned from the function as if a return was in front of it. Hence

var sq = fn (a) a^2

is equivalent to the named function

fn sqr(a) {
  return a^2
}

The anonymous function syntax is particularly useful for supplying to other functions, because quite often such functions end up being quite short. The List class, for example, provides a sort method that can be used to sort the contents. You can optionally provide a sort function that compares two elements of the list \(a\) and \(b\); this function should return a negative value if \(a<b\), a positive value if \(a>b\) and \(0\) if \(a\) and \(b\) are equal. This example sorts the list in reverse order

var lst = [5,2,8,6,5,0,1,3,4]
lst.sort(fn (a,b) b-a)
print lst

In some languages, anonymous functions are referred to as lambda functions, referring to the pioneering work of Alonzo Church on the theory of computation.

Scope

Functions obey scope so functions can be defined locally within a code block as in this example

{
  fn f(x) { return x^2 }
  print f(2) // prints 4
}

print f(2) // Raises an error

The function f remains available for the rest of the code block, but is not visible outside of it. Hence, while the first call works, the second throws SymblUndf as f is no longer visible.

Closures

Functions can be returned from other functions; such functions are known as closures for reasons that will become apparent shortly. Here's an example

fn inc(a) {
  fn f(x) { return x + a }
  return f
}

var add = inc(5)
print add(10) // prints 15

The function inc manufactures a closure that adds a given value a to its argument. This can be a bit complicated to follow, so let's trace out the sequence of events:

  1. inc is called with the argument 5. During the call, the parameter a takes on this value inside the inc function.

  2. A closure using the local function f is created within inc using the provided value of a (i.e. 5).

  3. The closure is returned to the calling code. The value of a remains available to the closure.

  4. The user calls the closure with the argument 10; the closure adds 5 to this and returns 15 which is displayed.

Closures are so-named because they enclose the environment in which they're created. In this example, the value of a is encapsulated together with the function f, forming the closure. The quantity a as is sometimes called an upvalue, because it's not local to the function definition; a is said to be captured by the closure.

Upvalues can be written to as well as read from. This closure reports how many times it has been called

fn counter(val) {
  fn f() { val+=1; return val }
  return f
}

var c = counter(0)
print c() // prints 1
print c() // prints 2
print c() // prints 3

Closures can be called anywhere regular functions can. An important use of closures is to create functions that obey a defined interface, i.e. they have the same signature, but have access to additional parameters. This example creates a function that describes the electric scalar potential due to a point charge with given charge and position

fn scalarPotential(q, x0, y0) {
  fn phi(x,y) {
    return q/sqrt((x-x0)^2 + (y-y0)^2)
  }
  return phi
}

var p1 = scalarPotential(1, -1, 0)
var p2 = scalarPotential(-1, 1, 0)

print p1(0,1) + p2(0,1)

The closures created can then be called with position of interest, returning the appropriate value. This could be useful in a larger code, where potential functions for many different types of entity are to be created, but each type requires very different data to specify them. Nonetheless, because all such potential functions obey the same interface, they can be used interchangeably.