Zum Inhalt springen
Developer Preview — APIs and language features may change before 1.0

Expressions

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

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 : valueIfFalse

Let 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

ReferenceWhat 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
}
MethodDescription
.map(fn)Transform each element
.flatMap(fn)Map then flatten one level
.filter(fn)Keep elements that match
.reject(fn)Remove elements that match
.reduce(fn, init)Accumulate a value
.find(fn)First matching element
.some(fn) / .any(fn)True if any element matches
.every(fn)True if all elements match
.includes(val)Check if value exists
.indexOf(val)Index of first occurrence (-1 if not found)
.sort()Sort elements
.sortBy(fn)Sort by a key function
.reverse()Reverse order
.unique() / .uniq()Remove duplicates
.uniqueBy(fn)Deduplicate by key function
.groupBy(fn)Group into object by key function
.flat()Flatten one level
.concat(other)Concatenate two arrays
.join(sep)Join elements into a string
.take(n)First N elements
.drop(n)All but first N elements
.chunk(n)Split into chunks of size N
.zip(other)Pair elements from two arrays
.slice(start, end)Sub-array by index range
.at(index)Element at index
.count(fn)Count elements matching predicate
.sum()Sum of numeric elements
.min() / .max()Minimum / maximum element
.first() / .last()First / last element
.lengthNumber of elements

String methods

MethodDescription
.toUpperCase() / .toLowerCase()Case conversion
.trim()Remove leading/trailing whitespace
.trimStart() / .trimEnd()Remove whitespace from one end
.split(separator)Split into array
.replace(from, to)Replace first occurrence
.replaceAll(from, to)Replace all occurrences
.startsWith(s) / .endsWith(s)Check prefix/suffix
.includes(s)Check if substring exists
.padStart(n) / .padEnd(n)Pad to length
.repeat(n)Repeat N times
.substring(start, end)Extract substring
.match(pattern)Regex match
.test(pattern)Regex test (returns boolean)
.lengthString length

Objects

Build objects with {} syntax. Use spread to merge:

compute merge
let base = {status: "active", created: DateTime.now()}
let details = {name: input.name, email: input.email}
{...base, ...details}

Access nested fields with dot notation: steps.analyze.results[0].name.

FunctionDescription
Object.keys(obj)Array of keys
Object.values(obj)Array of values
Object.entries(obj)Array of [key, value] pairs
Object.fromEntries(pairs)Object from [key, value] pairs
Object.merge(a, b)Merge two objects (b overrides a)
Object.hasKey(obj, key)Check if key exists
Object.get(obj, key)Get value by key
Object.delete(obj, key)Remove a key

Math

FunctionDescription
Math.round(x)Round to nearest integer
Math.round(x, n)Round to N decimal places
Math.ceil(x) / Math.floor(x)Round up / down
Math.abs(x) / Math.sign(x)Absolute value / sign
Math.pow(base, exp)Exponentiation
Math.sqrt(x) / Math.log(x)Square root / natural log
Math.min(a, b) / Math.max(a, b)Minimum / maximum
Math.random()Random number 0-1
Math.PI / Math.EConstants
Math.sin(x), cos, tan, asin, acos, atanTrigonometry

Formatting (Intl)

Format values for display. Returns strings, not numbers.

compute display
let price = Intl.formatNumber(input.amount, {decimals: 2}) // "1,234.50"
let rate = Intl.formatNumber(input.percentage, {style: "percent"}) // "18%"
let date = Intl.formatDate(input.timestamp, {style: "date"}) // "2026-06-09"
{price: price, rate: rate, date: date}
FunctionDescription
Intl.formatNumber(x)Format with thousands separator
Intl.formatNumber(x, {decimals: n})Fixed decimal places
Intl.formatNumber(x, {style: "percent"})Percentage format
Intl.formatDate(iso)Short date format
Intl.formatDate(iso, {style: "iso"})Full ISO 8601
Intl.formatDate(iso, {style: "date"})Date only
Intl.formatDate(iso, {style: "time"})Time only

DateTime

FunctionDescription
DateTime.now()Current ISO 8601 timestamp
DateTime.today()Current date
DateTime.parse(str)Parse ISO string
DateTime.diff(a, b)Difference between datetimes
DateTime.add(dt, amount)Add duration

JSON

FunctionDescription
JSON.parse(str)Parse JSON string to object
JSON.stringify(obj)Convert object to JSON string

Practical examples

Conditional formatting

compute format_price
let amount = input.price
{
display: amount > 1000
? "$" + Math.round(amount / 1000, 1) + "k"
: "$" + Math.round(amount, 2),
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

  • compute steps: the entire body is an expression
  • decide conditions: when <expression>
  • with task strings: ${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