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).
An action that writes nothing, emits nothing and makes no changing call,
itself or through any action it calls, is read-only. You don't mark it:
the compiler works it out from what the action does. A read-only action can
answer a GET, as a query can
(A GET that runs an action).
Here is a function. Save this as main.jadpo and run jadpo test main.jadpo:
type Stock = Int { min: 0}type Quantity = Int { min: 1 max: 1000}/// 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}test "six packs of four need 24" { assert stock_needed(4, 6) == 24}
Int is built in. Stock, Quantity and stock_needed are defined here.
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 aee2e8f.