Functions and Closures - mfichman/jogo GitHub Wiki
Functions
Functions can divide complex tasks into many smaller tasks. They have a name, return type, and parameters:
fibonacci(n Int) Int {
if n > 2 {
ret fibonacci(n-1) + fibonacci(n-2)
} else {
ret n
}
}
This is the recursive definition of the Fibonacci sequence. It takes one Int
parameter and returns an Int
parameter. Note: Jogo uses the ret
keyword rather than return
, as a nod to x86. This function also happens to be recursive, and is thus tail-call optimized. Here is another function that takes no value and returns no value:
print_hello() {
print("Hello")
}
Functions in Jogo are first-class objects. This means that they can be stored in variables and passed around. For example:
a = print_hello
a()
or, equivalently,
a = func() {
print("hello")
}
a()
Note that func
is a special keyword that is used to define a function literal, that is, an anonymous function. Functions can also be passed to other functions as arguments:
sort(array Array[Int], compare Function[Int,Int,Bool]) {
less = Array[Int]()
greater = Array[Int]()
if array.length <= 1 {
ret array
}
pivot = array[0]
for item in array {
if compare(item, pivot) {
less.append(item)
} else {
greater.append(item)
}
}
ret sort(less) + [item] + sort(greater)
}
This is a (very inefficient) implementation of a quicksort, using compare
as the criterion for sorting the array. Above, compare
takes two arguments of type Int
and returns a Bool
. One might call this function like so:
sort(["apple", "pear", "banana"], func(a Int, b Int) Bool {
ret a < b
})
Closures
When a function is defined, it can use variables that are visible from within the function. For example, the following code creates a function that prints all the values of array
starting at i
:
array = ["one", "two", "three", "four"]
i = 1
iterate() String {
if i < array.length {
print(array[i])
}
i = i + 1
}
Thus, iterate
is a special kind of function, called a closure, because it "closes over" i
and array
. Calling iterate()
repeatedly will yield each value in the array in turn (notice that #
defines the start of a comment):
iterate() # prints "two"
iterate() # prints "three"
iterate() # prints "four"
...
However, if i
were defined within iterate
, like so:
iterate() String {
i Int
if i < array.length {
print(array[i])
}
i = i + 1
}
then the behavior would be quite different, because the i
inside iterate
now shadows the i
outside iterate
. In fact, it is a completely different variable. The resulting output would be:
iterate() # prints "one"
iterate() # prints "two"
...
Note also that there is a difference between value-type variables and reference-type variables when it comes to closures. Value-type variables are always passed by value (copied) and so when a closure is created, the variable i
above is copied. Reference-type variables, such as array
above, are passed by reference, and so array
within iterate
is an alias to the array object. Thus, another function may modify the contents of array
from outside of iterate
.