13.5 Functions: the signature is the contract
- Approved by
- Panaversity ·
In everyday life. A vending machine has a sign: put in $2.00, press B4, get a bottle of water. You can check its promise from the outside. The sign tells you what goes in, what comes out, and what it promises. If you put in $2.00, press B4 and get orange juice, the machine broke its promise, and you know it without opening it. Code is the same: try it with an answer you already know.
What you will learn. How to read the first line of a named piece of code as a promise, test the promise with one line you can check by hand, and see what strict mode does when that first line has no labels.
Why it matters. A test shows a broken promise before you read the body, the code that does the work.
A function is a named piece of code that takes inputs and gives back a result. The inputs are its parameters. The values you pass in when you use it are its arguments. What it gives back is its return value. A function with no return line gives back nothing: undefined in TypeScript, None in Python. The first line of a function, with its name, its parameters and their types, and its return type, is its signature. The signature is the sign on the machine. It promises the kind of answer, not the right answer: a return type of string accepts any text. So only a test shows that the answer is right.
What does a signature promise?
What you must give the function, and what you get back. Here is one from Brightline: dueDate(invoiceDate: string, terms: string): string.
| Part of the signature | What it promises | In dueDate |
|---|---|---|
| Name | What the function is for | Works out a due date |
| Parameters with types | What you must give it | An invoice date as text, and the terms as text |
| Return type | What you get back | A due date as text, the last : string |
| What it does not say | Anything else must be in a test | That unknown terms are an error |
Why one job each?
A function that does one job can be named in a few words and tested on its own. It is like a fuse box with one fuse per room: when a light goes out, you know which fuse, and the rest of the house stays lit. A function that reads a file, computes due dates and prints a report is really three functions in one. When an agent writes a long function, ask it to split it, and then test each piece. The writer of the Nature column explains why from his own experience. With small, single-purpose functions, "I can isolate, interrogate and fix the faulty function without jeopardizing the rest of the pipeline"1. In other words, he can find and fix one bad function without breaking the rest.
The easiest functions to test. A function is pure when its result depends only on its inputs, and it changes nothing outside itself. Pure functions are easy to test. A pure function is like a multiplication table: 7 times 8 is 56 today, tomorrow, and on any computer. dueDate("2026-10-07", "Net 30") gives the same answer every time, on any computer, so a test can check it. A function that reads today's date from the computer's clock gives a different answer tomorrow. So Brightline's scripts take the cutoff date as an input, never from the clock.
Why must the math be done by a function, not a reply?
A pure, tested function is a small calculating engine: code whose only job is to compute an answer from its inputs, the same way every time. It is like an adding machine that prints a tape. A model's reply is more like a colleague adding in their head: often right, with no tape to check. If you ask it in a chat to add up thirteen invoice amounts, it may give a total that looks right but is wrong. It may also give a different total the next time.
The rule for every number. AI can reason about complex problems, but deterministic engines should enforce accounting rules and mathematical consistency. A deterministic engine is one that gives the same output for the same input, every time, on every computer. So when you need a number, ask the agent to write the function and its tests, then run the function. Never accept a total, a date or a tax amount that a model worked out in its reply.
Being deterministic makes a result repeatable, but not right. A wrong rule gives the same wrong answer every time, like a spreadsheet formula with the wrong tax rate in it. Confidence comes from rules you agreed, expected results you checked by hand, tests and review.
What is an assertion?
An assertion is a line that states what must be true, and stops the program with an error if it is not. It is the smallest possible test. You will decide many of them.
At work. Before Ana reads the body of a new function, she reads only its signature and says what it promises: "Given an invoice date and terms, it returns the due date." Then she picks one case she can check on a calendar, and the agent writes it as an assertion. An invoice on October 7 with Net 30 terms is due November 6. If the assertion passes, she reads the body. If it fails, she has found the bug without reading the body at all.
In TypeScript. The two import lines bring in ready-made code: assert, and dueDate from its file. Predict: which line prints, and what stops the program?
import assert from "node:assert/strict";
import { dueDate } from "./src/due-date.ts";
assert.equal(dueDate("2026-10-07", "Net 30"), "2026-11-06");
console.log("first assert passed");
assert.equal(dueDate("2026-10-07", "Net 30"), "2026-11-07");
console.log("second assert passed");first assert passed
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected
+ '2026-11-06'
- '2026-11-07'The second assertion expected the wrong day, and the program stopped with the actual and expected values side by side.
In Python. The signature is def due_date(invoice_date: str, terms: str) -> str:. The -> str is the return type.
from brightline_ap.due_date import due_date
assert due_date("2026-10-07", "Net 30") == "2026-11-06"
print("first assert passed")
assert due_date("2026-10-07", "Net 30") == "2026-11-07", "Net 30 from Oct 7"
print("second assert passed")first assert passed
Traceback (most recent call last):
...
AssertionError: Net 30 from Oct 7Where the languages differ. In TypeScript, assert comes from Node's built-in node:assert/strict module. A module is a file of ready-made code that a program imports, and Chapter 14 covers modules. assert.equal shows both values when it fails. In Python, assert is part of the language, and a plain assert shows only your message. pytest is the tool that runs Python tests. It makes a plain assert show both values, the way Node does.
What if a function has no labels?
Then its first line promises nothing, and an agent can write a function like that. Strict mode treats it as an error. You turn it on with strict in tsconfig.json, the file of TypeScript settings, and with typeCheckingMode = "strict" for Pyright. This function adds up a list of amounts, with a loop like the ones in 13.4, and it has no type labels. It runs, and only strict mode reports a problem.
In TypeScript.
function totalDue(amounts) {
let total = 0;
for (const amount of amounts) total += amount;
return total;
}
console.log(totalDue([482500, 61875]));$ node total-due.ts
544375
$ npx tsc --noEmit --strict --ignoreConfig total-due.ts
total-due.ts(1,19): error TS7006: Parameter 'amounts' implicitly has an 'any' type.npx tsc runs the TypeScript type checker on one file, here in strict mode and without the project's settings. any means any kind of value at all, so the checker has nothing to check amounts against.
In Python.
def total_due(amounts):
total = 0
for amount in amounts:
total += amount
return total
print(total_due([482500, 61875]))$ uv run python total_due.py
544375
$ uv run pyright
total_due.py:1:15 - error: Type annotation is missing for parameter "amounts" (reportMissingParameterType)Pyright reports more than one error here, and the output shows the one that names the cause. The fix is the label, a list of numbers: amounts: number[] and amounts: list[int]. Without strict mode, Pyright reports no errors here.
The operating rule: read a function's signature first and say its promise in one sentence, then test the promise with an assertion you can check by hand, before you read the body.

Figure 13.6. The signature is the contract. What goes in, what comes out, and the case you can check by hand.
Practice it. Practice: functions, in your own tool, fifteen minutes.
Check yourself
Question 1 / 9
0 answered
The signature promises an int.def open_total(cents: list[int]) -> int:
total = 0
for c in cents:
total += c
print(open_total([210000, 34500]))What does it print?
Sources
- Owner
- Panaversity