Functions
Edit on GitHubFunctions are essential to any language. They allow us to reuse code and solve complex problems.
Defining Functions
Defining a named function is similar to naming any other value in Grain.
The parameters of a function are put in parentheses, and functions evaluate to an expression. Unlike other languages, you do not need to use an explicit return
keyword to return a value (though the return
keyword does exist in Grain; more on that later).
Like in other places, Grain infers the parameter and return types automatically based on their usage. In this particular example, both of the parameters to this function and its return type were inferred to be Number
. Here is the same example with parameter types specified explicitly:
Real-world functions are usually more complex than a simple expression, so using block expressions is often desirable. In this case, the function will effectively return the last expression in the block.
Calling Functions
Functions can be called with each argument passed either positionally or by name:
1 | let add = (x, y) => x + y |
Functions as First Class Citizens
Since functions are just like any other values in Grain, they can be passed as arguments to other functions.
1 | module Main |
Furthermore, functions can return functions themselves!
1 | module Main |
Returning multiple Values
You can use tuples to return multiple values from functions.
1 | module Main |
Recursive Functions
We can define recursive functions using the rec
keyword. Recursive functions are a key part of Grain, so remember to use let rec
when necessary!
1 | module Main |
Early return
The return
keyword can be used to explicitly cut the execution of a function short. Note that if return
is used somewhere in a function, the remaining places where a value is returned must also use the return
keyword
return
can also be used without a value, in which case void
is returned implicitly
Infix Operators
Custom infix operators can be defined like regular functions, with the desired operator surrounded by parentheses.
Default Arguments
Function parameters can be given a default value, and if the caller does not supply an argument value the default will be used. Note that if a parameter has a default value, the corresponding argument must be passed by name.
1 | module Main |
Parameters with default arguments can be placed anywhere in the parameter list. Furthermore, positional arguments supplied to the function when invoked will only be applied to required parameters.
Closures
Grain functions have access to values defined in their enclosing scope(s). In technical terms, Grain will automatically create a closure for you when a function uses a value defined outside of its parameter list.
1 | module Main |
The log
function doesn’t define any bindings itself, but it has access to run
‘s mutable binding toLog
. When the log
function is called, it utilizes the current value stored in toLog
.
Furthermore, function closures will continue to “remember” values even when they’re used outside of their original scope. Here’s an example that makes a counter:
The makeCounter
function returns a counter function which will print sequential numbers when called.