Functions
Functions in Do are first-class values. They close over their lexical scope and can be stored in variables, passed as arguments, and returned from other functions.
def
Define named functions with def:
Functions with no arguments must have () after their name:
Implicit Return
A function returns the result of its final statement:
| Statement | Result |
|---|---|
| command | function return value |
let |
value of right-hand side |
bind |
value of scrutinee |
if (with final else) |
result of branch |
if (without final else) |
nil |
try/catch |
result of try (no error) or invoked catch |
All other statements have a nil result.
Explicit Return
Use return for early exit:
Parameters
Positional Parameters
Key Parameters
Key parameters use key: param syntax in the definition:
Ditto Key Shorthand
The :key shorthand declares a key parameter bound to a variable of the
same name:
The shorthand also works at call sites to pass a variable as a key argument:
let name = "Alice"
let age = 30
create_user :name :age
# equivalent to: create_user name: $name age: $age
Default Values
Both positional and key parameters support defaults:
def greet name = "World"
echo "Hello, $name!"
greet() # Hello, World!
greet Alice # Hello, Alice!
def connect :host = "localhost" :port = 8080
echo "Connecting to $host:$port"
connect() # localhost:8080
connect port: 3000 # localhost:3000
connect host: example.com # example.com:8080
Defaults are evaluated on every invocation of the function, so the following
function always returns a fresh empty array if called with no arguments:
Defaults are evaluated left-to-right and may reference earlier parameters:
def window start end = (start + 10)
start..end
assert_eq (window 5) (5..15)
assert_eq (window 5 20) (5..20)
Variadic Parameters
Use ... to accept extra arguments:
The rest parameter receives a record of the extra arguments,
in invocation order. Read a key argument by indexing, as in rest[:name:].
Iteration yields key/value tuples, where the key is the positional argument
index (0-origin) for positional arguments.
def echo_all ...args
for k v = args
echo "$k: $v"
echo_all foo bar: 1 baz
# prints:
# 0: foo
# bar: 1
# 1: baz
* accepts only extra positional arguments, and ** only extra key
arguments. *args receives a tuple and **kwargs a record. Both may appear,
* first, but neither combines with .... An extra argument that no rest
parameter accepts is an error:
def run cmd *args **opts
echo $cmd $args $opts
run ls -l -a color: always
# prints: ls ("-l", "-a") (color: "always")
Without a name, * and ** accept the arguments without binding them.
Argument Spreading
Spread collections or iterators into a call:
let args = [1, 2, 3]
func ...args
# equivalent to: func 1 2 3
let kwargs = {name: "Alice", age: 30}
func ...kwargs
# equivalent to:
# func name: "Alice" age: 30
Vertical Parameter Layout
Parameters in def can use vertical layout:
do Blocks
Anonymous functions (blocks and lambdas) are created with do:
Statement Context
In statement context, do without a following newline creates a one-statement
block:
With parameters:
If an immediate newline and indented block follows, it creates a multi-statement block:
Expression Context
In expression context, do creates a lambda where the body is an expression:
Non-Local Flow Control
break, continue, and return work through do blocks:
breakexits the innermost enclosing loopcontinueskips to the next iteration of the innermost enclosing loopreturnexits the innermost enclosingdef
This allows natural flow control in callbacks and higher-order functions:
def validate_record record
for field = ["id", "name", "email"]
record.get field else: do
return {valid: false, missing: field}
{valid: true}
A non-local branch will only be effective for the duration of the statement that introduces its containing closure; after this it will propagate a runtime error. The compiler requires that a closure containing non-local branches be in argument position to reduce the likelihood of this sort of error:
# Valid: closure is passed as an argument
def validate record
record.get "name" else: do
return false
true
# Invalid: closure is bound to a variable first
def bad_example()
# Compiler will reject this line
let closure = do return false
record.get "name" else: $closure
Public Functions
Use pub def to export a function from a module:
See Modules for details on the module system.
Decorators
Function defs may be preceded by decorators:
See Decorators for syntax, ordering, and examples.