Functions and actions
On this page
You write behaviour in three kinds of callable:
- A function is pure: it works out a result from its arguments and nothing else.
- An action is an operation of your app. It may read and write stored data, call outside services and call other actions.
- A query reads stored data and never changes it (Named queries).
Here is a function:
/// Total for one order line before discounts.function stock_needed(per_pack: Stock, packs: Quantity) -> Stock { var total: Stock = Stock(per_pack * packs) return total}
And an action, which loads a todo, changes it and returns a view of it:
action complete_todo(todo_id: Todo.id) fails TodoNotFound -> TodoView { var todo: Todo = attempt Todo.get(todo_id) var done: Todo = Todo.update(todo) { set: { status: TodoStatus.done } } return TodoView(done)}
Each kind may call only some of the others:
| Caller | May call | May not |
|---|---|---|
function |
functions, library functions, the standard library | actions, queries, stored data, outside services, clock.now |
query |
functions and queries | actions, writes |
action |
functions, queries and actions | |
A route's run: |
one action | a function or query directly (What a route runs) |
A function may still fail: it can declare fails and reject
(Failures). There is no async, await or promise:
every call finishes before the caller carries on.
Signatures and settings
Parameters are positional, and every one has a type. The result type
follows ->, and fails comes before it. A callable that returns nothing
returns Unit. Parameters have no defaults.
action reschedule(todo_id: Todo.id, due_at: DueAt) fails TodoNotFound, DueDateInPast -> TodoView { var todo: Todo = attempt Todo.get(todo_id) ...}
Your own callables are called with positional arguments. Named arguments
exist only for standard-library operations, such as
temporal.resolve(date: d, zone: z, ...).
The whole signature is on the header line, and an action has no settings, because the safe choice is already the default:
- every action is all-or-nothing;
- every write checks that no one else changed the record since it was read;
- every read is current.
All-or-nothing actions and concurrent changes has the details. Who may call an action is set by Policy, not by the action.
When a header is too long, the formatter puts one parameter on each line
and keeps fails and -> after the closing bracket.
Calling
What comes before the dot names the owner of what you call. Inside your
own domain (a business area of your app, a folder under domains/; see
Project layout), you
call its actions and queries by name alone. A helper function used only
in its own file is called the same way.
A helper that several files share lives in a library, a named group of
functions and constants, and is called through it, as the standard
library is. Another domain's public actions are called through that
domain's name.
library rounding { function half_even(amount: ItemPrice) -> ItemPrice { ... }}var total: ItemPrice = rounding.half_even(subtotal)var line: Stock = stock_needed(per_pack, packs)var stock: Reservation = attempt inventory.reserve_for_cart(cart)var todo: Todo = attempt Todo.get(todo_id)var local: ZonedTime = temporal.in_zone(at, zone)
In order, those calls are: a function in the rounding library, a helper
in this file, an action of the inventory domain, an operation every
entity gets, and the standard library.
A library holds only functions and constants (see Constants).
Markdown for agents: /docs/functions-and-actions.md.Built from Jadpo main at a707efb.