Expressions
mashin has a built-in expression language used inside compute steps, decide conditions, and ${...} interpolation in task strings. It is JavaScript-inspired: if you know JS, you already know most of it. The key difference is that expressions are pure. There are no I/O primitives, no fetch(), no console.log(). Expressions compute; machines effect.
Operators
Arithmetic
+ - * / %Comparison
== != > < >= <=In decide steps, you can also use natural-language operators: is, is not, is greater than, is less than, is at least, is at most.
Logical
&& || !Natural-language: and, or, not.
Ternary
condition ? valueIfTrue : valueIfFalseLet bindings
Bind intermediate values with let. Bindings are scoped to the current step:
compute analyze let count = input.items.length let total = input.items.reduce((sum, x) => sum + x.amount, 0) let average = total / count {count: count, total: total, average: average}The last expression in a step is its return value.
Data access
| Reference | What it accesses |
|---|---|
input.<field> | Machine input fields |
steps.<step_name>.<field> | Output from a previous step |
context.<key> | Execution context values |
state.<field> | Machine state (reactive machines) |
event.<field> | Incoming event data (in subscribes handlers) |
Dot access and bracket access both work: input.name and input["name"] are equivalent.
String interpolation
Inside with task strings (and any string using ${}), expressions are evaluated and inserted:
ask classify, using: "anthropic:claude-sonnet-4-6" with task "Analyze this order.\n\nCustomer: ${input.customer_name}\nTotal: $${input.amount}\nItems: ${input.items.length}"Array methods
Arrays support the standard functional methods:
compute transform let names = input.users.map(u => u.name) let active = input.users.filter(u => u.active) let total = input.scores.reduce((sum, s) => sum + s, 0) let found = input.items.find(i => i.id == input.target_id) let has_admin = input.roles.includes("admin") { names: names, active_count: active.length, total_score: total, target: found, has_admin: has_admin }| Method | Description |
|---|---|
.map(fn) | Transform each element |
.filter(fn) | Keep elements that match |
.reduce(fn, init) | Accumulate a value |
.find(fn) | First matching element |
.includes(val) | Check if value exists |
.length | Number of elements |
.join(sep) | Join elements into a string |
Objects
Build objects with {} syntax. Use spread to merge:
compute merge let base = {status: "active", created: now()} let details = {name: input.name, email: input.email} {...base, ...details}Access nested fields with dot notation: steps.analyze.results[0].name.
Practical examples
Conditional formatting
compute format_price let amount = input.price { display: amount > 1000 ? "$" + (amount / 1000).toFixed(1) + "k" : "$" + amount.toString(), tier: amount > 10000 ? "enterprise" : amount > 1000 ? "business" : "starter" }Data reshaping
compute reshape let grouped = input.orders.reduce((acc, order) => { let key = order.status let existing = acc[key] || [] return {...acc, [key]: [...existing, order]} }, {}) {by_status: grouped, total: input.orders.length}Filtering and scoring
compute score_candidates let scored = input.candidates.map(c => ({ ...c, score: c.experience * 0.4 + c.test_result * 0.6 })) let qualified = scored.filter(c => c.score >= 70) { qualified: qualified, qualified_count: qualified.length, best: qualified.reduce((best, c) => c.score > best.score ? c : best, qualified[0]) }Where expressions are used
computesteps: the entire body is an expressiondecideconditions:when <expression>with taskstrings:${expression}interpolation- Step configuration: values in
returns,assuming, field defaults
Try it
Write a machine that accepts a list of numbers and uses a single compute step to return the count, sum, average, minimum, and maximum. Use let bindings for clarity.
Next steps
- Computation - Compute steps in depth
- Decisions - Using expressions in decide conditions
- compute reference - Full specification