Functions Are Values, and DataWeave Means It

Named functions, lambdas, and the positional shorthands — how first-class functions turn DataWeave from a mapper into a language you can compose.

By now you can reach into a payload and coerce what you find. That gets you values out. The next question is what you do to those values in bulk — apply a discount to every line item, keep only the paid orders, roll a list into a total. All three are the same move: hand a small piece of behavior to something that runs it for you. In DataWeave that piece of behavior is a value like any other, and once you see it that way the whole language clicks.

A function you can name

The header — everything above the --- — is where you keep the reusable parts. A named function is declared with fun:

%dw 2.0
output application/json
fun net(price) = price * 0.9
---
net(20)

Output: 18.0. That’s the whole thing — no return, no braces, no body block. A function is a name bound to one expression, and calling it substitutes the argument in. You can annotate the parameter and result types when you want the checker’s help:

fun net(price: Number): Number = price * 0.9

The types are optional, but they document intent and catch a String you meant to coerce. Same function, a little louder about what it expects.

A function without a name

A lambda is a function you write inline instead of declaring up top. The syntax is parameters, an arrow, an expression:

(price) -> price * 0.9

That value on its own does nothing — it’s a function sitting there, unapplied. It becomes useful the moment you hand it to something that will call it. Which is the actual point of this post.

Higher-order: functions that take functions

map walks an array and applies a function to each element. It doesn’t know or care what the function does — you supply that. Say the payload is an order:

{
  "orderId": "A-1001",
  "items": [
    { "sku": "PEN-01", "price": 2.5, "qty": 4 },
    { "sku": "PAD-22", "price": 6.0, "qty": 2 }
  ]
}

Compute each line’s total by passing map a lambda:

%dw 2.0
output application/json
---
payload.items map (item) -> item.price * item.qty

Output: [10.0, 12.0]. map is a higher-order function — it takes another function as an argument. So is filter, so is reduce, and they’re the subject of the next post. Everything interesting in DataWeave is some data structure plus a function you feed it.

Because a function is just a value, the lambda doesn’t have to be anonymous. Name it once and pass it by name:

%dw 2.0
output application/json
fun lineTotal(item) = item.price * item.qty
---
payload.items map lineTotal

Same output. map calls lineTotal with each item and, incidentally, the index — extra arguments a function doesn’t declare are simply ignored, so a one-parameter function drops in cleanly. Naming the transform pays off the second time you need it.

The positional shorthands: $, $$, $$$

Most lambdas are short enough that naming the parameter is ceremony. DataWeave gives you positional placeholders: $ is the first argument, $$ the second, $$$ the third. For map, that’s the item and the index:

payload.items map $.price * $.qty

Identical result, less to read. In filter the same $ is the element; in mapObject and pluck, $, $$, and $$$ are the value, key, and index. The shorthand is idiomatic for one-liners. The moment a lambda grows past a line or nests another $, switch back to named parameters — $$ inside a $$ is a puzzle no one enjoys solving.

Infix or prefix — same call, different reading

payload.items map lineTotal reads like English because map is being called infix: left operand, function name, right operand. Every two-argument function in DataWeave can be written that way, and every infix call is also a plain prefix call:

map(payload.items, lineTotal)

Byte-for-byte the same operation. The trade is readability. A pipeline of transforms reads top-to-bottom left-to-right when it’s infix; deeply nested prefix calls read inside-out. This works for your own functions too — declare a two-parameter fun and call it with a word between its arguments:

%dw 2.0
output application/json
fun discountedBy(price, rate) = price * (1 - rate)
---
20 discountedBy 0.1

Output: 18.0. 20 discountedBy 0.1 and discountedBy(20, 0.1) are the same call. Reach for infix when it reads like a sentence, prefix when it doesn’t.

Currying: functions that return functions

A function’s body is an expression, and a lambda is an expression, so a function can return a function. That’s currying, and it’s how you make a specialized tool out of a general one:

%dw 2.0
output application/json
fun discount(rate) = (price) -> price * (1 - rate)
var tenOff = discount(0.1)
---
tenOff(20)

Output: 18.0. discount(0.1) doesn’t compute a discount — it hands back a new function that knows the rate and still wants a price. That returned function is a value you can store, pass around, or feed to map:

%dw 2.0
output application/json
fun discount(rate) = (price) -> price * (1 - rate)
var clearance = discount(0.3)
---
payload.items map clearance($.price)

You’ve turned one flexible function into a fixed, reusable one by supplying part of its input early — partial application. Note that a function value can’t be serialized to JSON; discount(0.3) on its own has nothing to output. It only earns its keep once you apply it.

Composition

Chaining transforms is just nesting calls — withTax(net(20)) runs net then withTax. When you find yourself repeating that pairing, capture it as its own function that takes two functions and returns their composition:

%dw 2.0
output application/json
fun net(p) = p * 0.9
fun withTax(p) = p * 1.2
fun compose(f, g) = (x) -> f(g(x))
var netThenTax = compose(withTax, net)
---
payload.items map netThenTax($.price * $.qty)

compose takes two functions, returns a third, and never mentions a price — it works on any two compatible functions. That’s the shape you keep meeting in DataWeave: small functions, passed by value, wired together into exactly the transform you need.

Final thoughts

Treating functions as values isn’t a party trick here — it’s the grain of the whole language. A named fun is a reusable transform, a lambda is a throwaway one, and map/filter/reduce are just functions that happen to take functions. Get comfortable passing behavior around and the rest of DataWeave stops being a pile of operators and starts being composition, which is the only idea you actually need.

Next: map, filter, reduce — and why reduce is the only hard one

Comments