Functions
A function in morpho is defined with the fn keyword, followed by the function’s name, a list of parameters enclosed in parentheses, and the body of the function in curly braces. This example computes the square of a number:
fn sqr(x) {
return x*x
}
Once a function has been defined you can evaluate it like any other morpho function.
print sqr(2)
Functions can accept optional parameters:
fn fun(x, quiet=true ) { if (!quiet) print "Loud!" }
Morpho functions can also be defined to restrict the type of accepted parameters:
fn f(List x) { print "A list!" }
Multiple implementations can be defined that accept different numbers of parameters and parameter types:
fn f() { print "No parameters!" }
fn f(String x) { print "A string!" }
fn f(Tuple x) { print "A tuple!" }
The correct implementation is then selected at runtime:
f("Hello World!") // expect: A string!
Variadic
As well as regular parameters, functions can also be defined with variadic parameters:
fn func(x, ...v) {
for (a in v) print a
}
This function can then be called with 1 or more arguments:
func(1)
func(1, 2)
func(1, 2, 3) // All valid!
The variadic parameter v captures all the extra arguments supplied. Functions cannot be defined with more than one variadic parameter.
You can mix regular, variadic and optional parameters. Variadic parameters come before optional parameters:
fn func(x, ...v, optional=true) {
//
}
Optional
Functions can also be defined with optional parameters:
fn func(a=1) {
print a
}
Each optional parameter must be defined with a default value (here 1). The function can then be called either with or without the optional parameter:
func() // a == 1 due to default value
func(a=2) // a == 2 supplied by the user
Note that optional parameters may not be typed.
Return
The return keyword is used to exit from a function, optionally passing a given value back to the caller. return can be used anywhere within a function. The below example calculates the n th Fibonacci number,
fn fib(n) {
if (n<2) return n
return fib(n-1) + fib(n-2)
}
by returning early if n<2, otherwise returning the result by recursively calling itself.
Signature
The signature of a function is a list of the types of arguments in its definition:
fn f(x) {} // Accepts one parameter of any type
fn f(x,y) {} // Accepts two parameters of any type
fn f(List x, y) {} // The first parameter must be a list
fn f(List x, List y) {} // Both parameters must be lists
While you can define multiple implementations of a function that accept different parameter types, you can only define one implementation with a unique signature.
Note that optional and variadic parameters are not typed.
Multiple dispatch
Morpho supports multiple dispatch, whereby you can define several implementations of a function that accept different types:
fn f(List x) { return "Accepts a list" }
fn f(String x) { return "Accepts a string" }
Morpho chooses the appropriate implementation at runtime:
f([1,2,3]) // Selects the List implementation
Any classes you define can be used as types:
class A {}
class B is A {}
class C is B {}
fn f(A x) { return "A" }
fn f(B x) { return "B" }
Morpho selects the closest match, so that:
print f(A()) // expect: A
print f(B()) // expect: B
print f(C()) // expect: B
Class C inherits from both B and A, but because it directly inherits from B, that’s the closer match.
Closures
Functions in morpho can form closures, i.e. they can enclose information from their local context. In this example,
fn foo(a) {
fn g() { return a }
return g
}
the function foo returns a function that captures the value of a. If we now try calling foo and then calling the returned functions,
var p=foo(1), q=foo(2)
print p() // expect: 1
print q() // expect: 2
we can see that p and q seem to contain different copies of g that encapsulate the value that foo was called with.
Morpho hints that a returned function is actually a closure by displaying it with double brackets:
print foo(1) // expect: <<fn g>>