Cicada functions are objects, like composite variables and sets (the analogy here is closer than you would think). So we define functions using the same operators we use for defining other objects. The function code goes inside curly braces beginning on the line of the definition. Here is an example.
SwapDigits :: {
tensDigit :: onesDigit :: int
code
tensDigit = floor(args[1] / 10)
onesDigit = args[1] mod 10
return new(10*onesDigit + tensDigit)
}
The function shows us several new keywords. 1) A ‘code’ marker (or semicolon) separates the function’s variable definitions from its executable code. Before the code marker, the function’s internal variables are defined in exactly the same way as global variables in the workspace are defined. 2) Function arguments are contained inside of an args variable, and we access them using the index operators []. The function exits with the classic return statement, just as in C except 3) we enclosed the return value inside of a new() function call.
Once we have written the function, we can run it in the normal way.
> print(SwapDigits(27))
72
From the command line we can also just type the function name and read off the output:
> SwapDigits(27)
72
Sometimes the automatic output can be annoying: the function may be performing an important task but we don’t care about the return value. To throw away the return value, append a ‘~’ operator to the function call: SwapDigits(27)~.
In Cicada we can treat functions as if they were variables, peek at all their internal members, and even fiddle around inside them. This is useful for debugging.
> SwapDigits.tensDigit | did it do what we wanted?
2
> remove SwapDigits.onesDigit | let's see if it still works..
Finally, functions can define other functions:
> SD2 :: SwapDigits
We broke SwapDigits() when we deleted its oneDigit member, but SD2 is defined using the original definition of SwapDigits so it will work just fine.
One situation that requires several copies of a function is recursion. This is because each nested recursive instance of the function requires its own storage. This is true whether a function f() calls itself directly or indirectly (f() calls g() calls f()). Here is an example that handles recursion properly.
factorial :: {
numToReturn :: int
code
if args[1] == 1 then numToReturn = 1
else numToReturn = args[1] * (new_fact :: this)(args[1]-1)
return numToReturn
}
Importantly, the definition new_fact :: this had to be in the coding section of factorial(). If we had put the definition new_fact() before the code marker, factorial.new_fact would have defined another member factorial.new_fact.new_fact, etc. until Cicada threw in the towel with a recursion-depth error.
Our last example might have handled recursion correctly, but it did something else quite wrong, as we find out by typing
> { factorial(3), factorial(4) }
{ 24, 24 }
The second call to factorial() overwrote the first! The reason is that our set consists of two aliases to numToReturn, whose value changes with each function call. The solution is to have factorial() redefine numToReturn each time it is run.
return new(numToReturn)
The new() function is defined in user.cicada which pre-loads when Cicada is run interactively. If user.cicada wasn’t loaded we have to redefine our return variable manually.
factorial :: {
numToReturn :: int
code
(numToReturn =@ *) :: int | redefining an unlinked member creates a new variable
if args[1] == 1 then numToReturn = 1
else numToReturn = args[1] * (new_fact :: this)(args[1]-1)
return numToReturn
}
Here is one unusual feature of Cicada functions:
f :: {
num := 2
code
num = that + 1
code
return num }
Two coding blocks---so there should be a way to access them both.
> f()
> f#2()
3
The code-number operator ‘#’ lets us run the code beginning at the Nth code marker. By running f(), which defaults to f#1(), we incremented f.num. But execution stopped at the next code marker. There was no return statement, which is fine: the function returns nothing, just as it would had we written return or return *. In order to get a value back we had to run the second coding block which returned f.num. Incidentally, had we run f#0() we would rerun f’s ‘constructor’ and find f.num reset to 2.
Short functions like f() are sometimes easier to code on one line. To do this we use the fact that commas are equivalent to ends-of-lines, and semicolons are equivalent to code markers.
> f :: { num := 2; num = that + 1; return num }
Last update: May 8, 2024