Why would a function ever want to run its arguments? One good reason is that an args() call is the easiest way for a function to accept optional parameters. To do this, we must write the function so that all of its optional parameters (or aliases to them) are grouped into one composite variable. When the function is called, it will first set those parameters to their default values, then run args() inside of the parameters variable with the help of the code substitution operator ‘<<’. Here is an example.
RollDice :: {
numDice :: total :: loopDie :: int
params :: {
numSides :: int
isLoaded :: bool }
code
{ numDice } = args
params = { 6, false }
(params << args)()
...
}
If we are happy with the default values of numSides and isLoaded, so we can leave them out.
RollDice(5)
The function call only needs a coding section when we want to cheat or roll exotic dice.
RollDice(5; isLoaded = true)
All members of params must be explicitly named. This would be an incorrect definition:
numSides :: int
isLoaded :: bool
params :: { numSides, isLoaded }
The problem is that this params is better viewed as a set -- neither element (member) has a name, so args() can’t easily change the parameters. A nameless member aliasing the numSides or isLoaded variable is not the same as a member named numSides or isLoaded.
In general, a code substitution D << F, involving composite variables or functions D and F, returns the data space of D along with the code of F. On the other hand, plain F returns both the data space and the code of F. The two basic properties of composite variables and functions are data and code, and the code substitution operator is the tool for separating and recombining these properties.
There are many uses for code substitution beyond optional function arguments. For example, start.cicada uses it to run the user’s commands inside of the workspace variable. Code substitution can save a lot of typing when working with awkward pathnames. For example, instead of
games.backgammon.RollDice.params.numSides = 20
games.backgammon.RollDice.params.isLoaded = true
games.backgammon.RollDice.params.dieColor := "green"
...
we can just write
(games.backgammon.RollDice.params << {
code
numSides = 20
isLoaded = true
dieColor := "green"
...
})()
Notice that we had to write all of our commands after a code marker, and run the substituted code as a function. That way the assignment commands will run inside of the params variable.
A code substitution is quite temporary: it only lasts as long as the immediate expression is being evaluated. The next time we run RollDice() it will be its normal self, except that its params variable will have a new member dieColor.
The code-number operator can be used to control which code is passed to the code-substitution operator. Here is a function that uses either one or two sets of optional parameters, depending on how it is called.
f :: {
...
code
params_1 :: { doMoreStuff := false, ... }
params_2 :: { otherNum :: int, ... }
(params_1 << args)()
if params_1.doMoreStuff then (params_2 << args#2)()
...
}
When we call this function, our arguments will contain 1, 2 or 3 coding blocks.
f(2, 5) | don't doMoreStuff
f(2, 5; doMoreStuff = true) | doMoreStuff w/ defaults
f(2, 5; doMoreStuff = true; otherNum = 12, ...) | change defaults
Last update: May 8, 2024