From 00edf1743be96fe04e698def542793a67c6c1d91 Mon Sep 17 00:00:00 2001 From: Santiago Date: Sat, 30 May 2026 18:22:51 -0300 Subject: [PATCH] docs(language): document user-defined functions Add a Functions guide page covering fn declarations, let-bindings, calling, the relationship to the built-in functions, and restrictions. Link it from the index and Data Expressions pages, and drop the removed bare 'tip_slot' identifier form. Renumber the trailing pages. Co-Authored-By: Claude Opus 4.8 (1M context) --- language/assets.md | 2 +- language/cardano.md | 2 +- language/comments.md | 2 +- language/data.md | 4 +- language/functions.md | 90 +++++++++++++++++++++++++++++++++++++++++++ language/index.mdx | 2 + language/txs.md | 2 +- 7 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 language/functions.md diff --git a/language/assets.md b/language/assets.md index 4ed29ff..c97d715 100644 --- a/language/assets.md +++ b/language/assets.md @@ -1,7 +1,7 @@ --- title: Assets sidebar: - order: 6 + order: 7 --- A UTxO carries value: a bag of `(policy, asset_name, amount)` triples. Tx3 surfaces this with the `AnyAsset` type and a small set of conveniences that make it easy to talk about quantities of specific assets without writing the policy and asset name out at every use. diff --git a/language/cardano.md b/language/cardano.md index 314f3cd..0037486 100644 --- a/language/cardano.md +++ b/language/cardano.md @@ -2,7 +2,7 @@ title: Cardano-specific Features sidebar: label: Cardano - order: 8 + order: 9 --- Most of the Tx3 language is chain-agnostic, but some transaction features are specific to one chain. On Cardano those features live under the `cardano::` namespace: stake operations, witnesses for script-using actions, reference-script publishing, treasury donations, and so on. diff --git a/language/comments.md b/language/comments.md index 5b87fdd..ca717f0 100644 --- a/language/comments.md +++ b/language/comments.md @@ -1,7 +1,7 @@ --- title: Comments sidebar: - order: 9 + order: 10 --- Tx3 has three kinds of comments: regular line comments, block comments, and doc-comments. The first two are stripped during parsing and have no effect beyond letting you annotate the source. Doc-comments are different — they are preserved and surfaced by downstream tooling (the registry UI, generated bindings, etc.). diff --git a/language/data.md b/language/data.md index 6fbefb1..63c6be1 100644 --- a/language/data.md +++ b/language/data.md @@ -137,12 +137,12 @@ Accessing a property that does not exist on the value's static type is a compile ## Built-in functions -These functions are always in scope. +These functions are always in scope. They use the same call syntax as [user-defined functions](./functions), and share the same namespace. | Call | Returns | Notes | | ------------------------- | ---------- | -------------------------------------------------------------------------------------- | | `min_utxo(output_name)` | `AnyAsset` | Minimum Ada the named output needs to satisfy the chain's min-UTxO rule. | -| `tip_slot()` | `Int` | The chain tip slot at resolution time. May also be written as the bare identifier `tip_slot`. | +| `tip_slot()` | `Int` | The chain tip slot at resolution time. | | `slot_to_time(slot)` | `Int` | Converts a slot number to a POSIX time. | | `time_to_slot(time)` | `Int` | Converts a POSIX time to a slot number. | | `concat(a, b)` | same as `a`| Concatenates two `Bytes` values or two `List` values; both arguments must have the same type. | diff --git a/language/functions.md b/language/functions.md new file mode 100644 index 0000000..40bd78d --- /dev/null +++ b/language/functions.md @@ -0,0 +1,90 @@ +--- +title: Functions +sidebar: + order: 6 +--- + +A function is a named, reusable piece of a [data expression](./data). When the same calculation shows up in several places — or when a sub-expression is complex enough to deserve a name — you can factor it into an `fn` and call it wherever a value is expected. + +Functions are a source-level convenience: they are *inlined* by the compiler, so a call leaves no trace in the resolved transaction. They add naming and reuse, not new runtime behaviour. They are pure, non-recursive, and cannot touch transaction state (inputs, outputs, `fees`, and the like). + +## Declaring a function + +```tx3 +fn double(x: Int) -> Int { + x + x +} +``` + +A function declaration has a name, a parenthesised parameter list, a return type after `->`, and a body in braces. Every parameter is typed, and the return type is explicit — there is no inference. + +Identifier rules match the rest of the language: a function name starts with a letter and contains letters, digits, and underscores, and must be unique across the program's top-level declarations (including the built-in functions below). + +## Function body + +A body is an optional sequence of `let`-bindings followed by a single result expression. The result is the value the function returns; its type must match the declared return type. + +```tx3 +fn discounted(base: Int, off: Int) -> Int { + let net = base - off; + let doubled = net + net; + doubled +} +``` + +Each `let` names the value of a data expression and is visible to the bindings that follow it and to the result. A body is itself just a data expression, so the usual operators (`+`, `-`, property access, indexing) and constructors are available — but transaction blocks, control flow, and recursion are not. + +Functions can return records (or any other type), and can call other functions: + +```tx3 +type PoolState { + pair_a: Int, + pair_b: Int, +} + +fn make_pool(a: Int, b: Int) -> PoolState { + PoolState { + pair_a: a, + pair_b: b, + } +} + +fn adjust_pool(pool: PoolState, delta_a: Int, delta_b: Int) -> PoolState { + PoolState { + pair_a: pool.pair_a + delta_a, + pair_b: pool.pair_b - delta_b, + } +} +``` + +## Calling a function + +Call a function with the same syntax as the [built-in functions](./data#built-in-functions): the name followed by parenthesised arguments. A call is valid anywhere a data expression is — output amounts, datums, locals, and so on. + +```tx3 +party Sender; +party Receiver; + +tx use_double(amount: Int) { + input source { + from: Sender, + min_amount: Ada(amount), + } + output { + to: Receiver, + amount: Ada(double(amount)), + } +} +``` + +The number of arguments must match the parameters, and each argument's type must match the corresponding parameter's type. + +## Functions and the built-ins + +The built-in helpers `min_utxo`, `tip_slot`, `slot_to_time`, and `time_to_slot` are functions too — they share the same call syntax and namespace, and you cannot define an `fn` that reuses one of their names. The difference is that the built-ins are evaluated by the resolver (using chain state), whereas user-defined functions are inlined away at compile time. See [Data Expressions](./data#built-in-functions) for their signatures. + +## Restrictions + +- Functions are top-level only; they cannot be nested. +- Functions must not be recursive, directly or through a cycle of calls — inlining would not terminate. +- A function body cannot reference a transaction's inputs, outputs, references, locals, or `fees`; it only sees its own parameters and `let`-bindings, plus program-level names (parties, policies, assets, types). diff --git a/language/index.mdx b/language/index.mdx index 30cc82f..614a721 100644 --- a/language/index.mdx +++ b/language/index.mdx @@ -33,6 +33,8 @@ The right-hand side of every Tx3 clause is a data expression — a literal, a co + + ## Transaction templates The core declaration in a Tx3 file is a `tx`: a parameterised template that the toolchain turns into a concrete, balanced transaction. A `tx` body is a collection of blocks — inputs, outputs, mints, witnesses, validity bounds, signers, metadata, locals, collateral — combined freely. diff --git a/language/txs.md b/language/txs.md index a84fa99..1bd9c1e 100644 --- a/language/txs.md +++ b/language/txs.md @@ -1,7 +1,7 @@ --- title: Transactions sidebar: - order: 7 + order: 8 --- A `tx` declares a transaction *template*: a parameterised description of a transaction that the toolchain resolves into a concrete, balanced, ready-to-submit transaction at the moment of use. The body of a `tx` is a collection of blocks — inputs, outputs, mints, witnesses, metadata, and so on — that together describe what the transaction must contain.