Notice that the definition of a Cicada function doesn’t specify any argument list. That means functions in Cicada are automatically able to handle different argument types, variable numbers of arguments, etc. Here is a simple function that can take any arguments whatsoever, as long as there are at least 2 of them.
> arg2 :: { code, return args[2] }
> arg2(5, 3+9, 'C') | return a number
12
> arg2(pi, { a := 3, b := 4; return a*b }) | return a function!
{ 3, 4 }
Cicada functions also don’t specify return types, and as our last example shows a single function can return different kinds of values even from the same return statement. And of course different return statements within the same function can return different types of objects.
What happens if we stick a code marker (or semicolon) inside the function’s argument list? When we try this experiment, we’ll find out is that anything after the function arguments’ code marker isn’t created, or doesn’t run, until the function runs its arguments.
PrintArgs :: {
code
print("Before running args: ", args, "\n")
args()
print("Afterwards: ", args, "\n")
}
If we call
PrintArgs( 0.3, " 4", code, " word ", 10 )
then the function prints
Before running args: 0.3 4
Afterwards: 0.3 4 word 10
We can even put coding statements in the function arguments.
PrintArgs( " *** announcement *** ", code, print("I am an argument list.\n") )
which causes the output
Before running args: *** announcement ***
I am an argument list.
Afterwards: *** announcement ***
In fact an argument list can run any code whatsoever. A few tricky points: if we define variables, etc. inside of an argument list then they will only exist inside that function’s args variable; this inside the argument list refers to args, not the variable space where the function was called (that will be parent); and return inside of args only stops execution of args’s code.
If we can run the args list, surely we can also pass it parameters? Write a new function to test this:
> doArgs :: { code, args(9, "lives"), return args }
> doArgs( args, code, print(top(args), " arguments were passed\n") )
2 arguments were passed
This example is complicated, particularly the function call (2nd command entered at the prompt). On this line, within the parentheses, ‘args’ refers to two different things depending on which side of the code marker it falls on. Before the code marker, args holds the same value it had when the function was called. But after the code marker, ‘args’ is the parameter list passed by doArgs, containing the number 9 and the string "lives". Each function call temporarily replaces the existing args variable with its own argument list; the old args comes back when each function exits.
Here’s another example showing more explicitly how args changes across the code marker.
f :: {
code
g( print(args, " --> "), code, print(args) )
}
g :: { code, args("B") }
That is, f() runs g(), which in turn runs its own arguments. Now run f().
> f("A")
A --> B
Focus on the line in which function f() calls function g(). The part of args before the code marker predates g(), so here args still has its old value ‘A’. The part of args after the code marker is called by g(), so here args is ‘B’.
Finally, function arguments can contain return statements just like normal functions.
> doTwice :: { num :: double, code, num = args[1], return args(args(num)) }
> doTwice(3, code, return args[1]^2) | calculate (3^2)^2
81
To summarize, Cicada’s function arguments are themselves functions. To make this clearer, we can imagine a syntax that uses curly braces for function calls:
doTwice{ 3, code, return args[1]^2 }
In fact this syntax is legal! The only difference with a regular function call is that the argument space will linger as a new object after the function call, wherever that call happened. (Technically the argument space always persists but is normally hidden). This way of writing a function makes clear that the arguments are just some function object that’s ordinarily invisible, but has an args alias inside of the running function.
The args variable is usually defined inline, using parentheses or braces, but it can also be aliased from an existing variable or function using the familiar @ operator. For example:
f :: { ; return args + 1 }
g :: { ; return args*2 }
a := 5
print(g @ f @ a)
This produces a notation similar to the ‘circle’ syntax for function composition.
Last update: November 12, 2025