One of the most valuable characteristics of DataWeave is that it is a functional programming language. This means it is dynamically able to solve problems with various approaches — one being currying, which is a common feature of functional programming languages like Haskel (from where it derives), and JavaScript.
In this post, I’ll explain what currying is and how to write curried functions in DataWeave.
What is currying?
Currying is about decomposing a function that takes multiple arguments into numerous functions that take just a single argument. For example, a function that takes two arguments multiply(x, y), and then when the function is curried it can take one argument at a time multiply(x)(y). The value x can be fixed, then sometime later the value y is fixed. Now that all parameters are fixed, the body of the function is executed and the result returned. This is a curried function.
What is a curried function?
How does a function only allow some of its parameters to be fixed? Taking the above example of a two-parameter function that multiplies its parameters. When the value x is fixed the return from fixing x is another function. This function accepts only one parameter, which is the y parameter. When this is fixed the function is executed.
Why is currying useful?
Currying helps you avoid passing the same variable again and again. If x is fixed then the returning function can be reused multiple times and x will not need to be fixed again on that function. It creates a higher-order function. This is a function that takes a function as an argument, and returns a function. Therefore, increasing the opportunity to reuse functionality.
Simple DataWeave function
Let’s look at a simple DataWeave function that multiplies two numbers:
fun multiply(x, y) = x * y
This function must be called with two parameters. It fixes those values and operates on them and returns the result, the multiple of its parameters:
multiply(10, 5) // 50
You can create functions that do one thing like, multiply by 10:
fun multiplyBy10(y) = 10 * y
It accepts one parameter which is then multiplied by 10:
multiplyBy10(5) // 50
You can continue to create functions like this.
Currying a DataWeave function
In these examples, the parameters must be known and passed to the function at the time it is executed. But what if you don’t know what all the parameters are at the same time? Perhaps you only know the first parameter and don’t know when you will know the second parameter.
One way to resolve this would be to store the first parameter in a variable and wait until you know the second parameter before passing them to the functional and executing it:
var x = 10
var y = 20
fun multiply(x, y) = x * y
However, it may be more convenient to fix the first parameter in the function and when the second parameter becomes known, fix and execute the function at that time. This is where currying can help.
Take a look at the following DataWeave curried function:
var multiply = (x) -> (y) -> x * y
It is functionally equivalent to the example discussed above (fun multiply(x, y) = x * y), however it will allow you to fix the x parameter before fixing the y parameter, as in the following example:
var partialApplication = multiply(10)
The x value has been fixed as 10 and the return value is another function that only expects one parameter, the y parameter. When the y parameter is passed the body of the function is executed, x and y are multiplied, and the result is returned, as shown in the example below, the y value is fixed as 5 and as x is already fixed as 10 the execution of the function returns the value 50:
partialApplication(5) // 50
This is referred to as the partial application of the function’s parameters as only some and not all parameters have been fixed, i.e. partially applied.
Partial application
What is a partial application? A partial application is a function that has had some of its parameters fixed inside its closure scope but not all. This is what we did above. The x parameter is fixed inside the function, resulting in a partially applied application (of the function).
The result of a partial application is a function that expects to receive the remaining unfixed parameter. Considering that DataWeave treats functions as first-class, it means that the resulting function of a partial application can be assigned to a variable and completed later.
Consider the following example that requires four parameters:
var fourDSpace = (x) -> (y) -> (z) -> (t) -> x * y * z * t
This curried function can be partially apply by passing only two parameters:
var twoDSpace = fourDSpace(10)(5) // returns a partially applied function
The x and y parameters are fixed in the partially applied functions twoDSpace. Now, I have a function referenced by the twoDSpace variable and I can fix the remaining parameters so the function can be executed:
var threeDSpace = twoDSpace(2) // returns a partially applied function
threeDSpace(100) // 10,000
Alternatively, all parameters can be fixed at the same time:
fourDSpace(10)(5)(2)(100) // 10,000
Partial applications allow you to create specifications of a function. The following function fixes a discount percentage, the principle to be discounted and the number format of the output:
var discount = (format) -> (discount) -> (principle) ->
( ( (100-discount) / 100 ) * principle ) as String {format: format} as Number
var reduce10Percent = discount(“#,###”)(10)
Now, I have a function based on the original function, that I can use to discount by 10%:
var trousers = reduce10Percent(150.00) // 135
var shoes = reduce10Percent(50.00) // 45
var hat = reduce10Percent(200.00) // 180
Functional composition and higher-order functions
Curried functions are useful in the context of functional composition. Referred to as higher-order functions, they are functions that accept another function as an argument, and return another function or a result. Consider the following examples:
var addOne = (n) -> n + 1
var multiplyByTwo = (n) -> n * 2
I can compose another function from these two functions:
var compositeFunction = (x) -> multiplyByTwo(addOne(x))
Now I can use this composite function:
compositeFunction(20)
The output of this function is (20 + 1) * 2 = 21 * 2 = 42.
Partial application and functional composition
A more involved example is to partially apply a function which is then passed into another function and then the final parameter is fixed.
For this example, I am going to use the scrolling marquee HTML element that was so popular back in the day. You can see that the direction of the scrolling marquee is fixed and the partial function is referenced with the openTag variable:
var marquee = (direction) -> (width) ->
“<marquee direction='” ++ direction ++ “‘ width='” ++ width ++ “‘>”
var openTag = marquee(“right”)
The template compiles the elements that configure the scrolling marquee include the opening and closing tag and the text to scroll.
var template = (openTag) -> (text) -> (closeTag) -> (width) ->
(openTag(width) ++ text ++ ”
-> width is ” ++ width ++ closeTag)
And finally, the template function is fixed. Note how the openTag partially applied function is passed to the template function and completed with the width function.
template(openTag)(“Retro HTML example”)(“</marquee>”)(500)
Conclusion
A curried function has many practical uses in the composition of reusable DataWeave scripts. With some study and plenty of practice, they can become a tool in the DataWeave developers’ toolbox. If you are interested in learning more about DataWeave, take the Anypoint Platform Development: DataWeave training course.
For reading resources, check out these blog posts: DataWeave lambdas for Java programmers, Generating XML with DataWeave, and DataWeave function chaining for Java programmers.