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 |
if |
result of selected branch, or nil if none runs |
match |
result of selected arm, or nil if none matches |
try/catch |
result of try (no error) or invoked catch |
All other statements, including let, bind and assignments, have a nil
result. To return a bound value, end with it:
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
See Spreading for what each kind of value contributes.
A keyword that names a parameter binds its first occurrence, including one
supplied through spreading. A ** or ... rest takes any repeats, and without
one they raise UnexpectedKeyError. A rest keeps the
keywords it takes in order, repeats included.
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
A for in vertical layout builds collection items or call arguments; it is not
a loop target. A break or continue in a do block inside one targets the
enclosing statement loop, and is an error if there is none.
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 works only while the statement that creates its block is running; after that, it raises an error. A block containing one must therefore be passed directly as an argument:
# 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.