Let's make some curry.
Tags that this post has been filed under.
As a preface, I think it goes without saying, but this article is just me trying to share about something that I'm learning. It's quite probable that I still have room to grow and can make mistakes in my understanding or in how I communicate the idea.
With that out of the way, let’s take a look at a concept that I have been wrestling with called “curried” functions.
Not to be confused with the delicious meal. Making "curried" functions is a pattern in programming that is not specific to a language, but it is geared towards the paradigm of functional programming. The idea is credited to Haskell B. Curry, but it is understood that the idea most likely originated from a colleague, Moses Schönfinkel. I think we can all agree that “curry” is much easier to say.
Let’s start by recalling what a function is. In my own words, functions are mechanisms that have one or more inputs and an output. The inputs are called parameters and the output is what is returned. All of my code examples for this article will be in JavaScript.
const add = (a, b) => a + b;
In the above code snippet, the function accepts the parameters of 'a' and 'b' and returns the sum of those two values. Simple enough, but one pitfall would be using the function and forgetting a parameter. For this example, it may be hard to fathom, but for complex functions it could be easy to forget a parameter or the intended purpose. This results in repeated code and emphasis on the developer being well versed in miniscule implementation details.
function iAmComplicated(a, b, specialFlag, isComplicated, specialOptions) { … };
Curried functions to the rescue!
Curried functions in a mathematical sense convert a single function with ‘n’ parameters to ‘n’ functions with a single parameter.
The following example builds on the original. However, there is one key difference. Can you see it? The ‘a’ and ‘b’ references are in different scopes, and this is where the magic happens. The returned function is a partially completed function that will always know about ‘a.’
const add = (a) => (b) => a + b;
const addOne = add(1);
console.log(addOne(9)); // 10
Now we are getting somewhere. Consider for a moment the implications. You can now build some modular functions that have “baked-in” values. As a caution, I would be judicious with the useage of this pattern. If used too often or in the wrong context, it can be problematic or difficult to reason about.
Examples
// An example with an embedded mongoose client
const wrapper = (client) => (request) => client.find(request);
const client = wrapper(mongoose);
const findUserById = client({ _id: userId });
// an example used with the higher order function “filter”
const users = [ { name: “bilbo” }, { name: “aragorn” }, { name: “gandalf” } ];
const filterByName = (name) => (item) => item.name !== name;
const noBilbo = users.filter(filterByName(‘bilbo’)); // [ { name: “aragorn” }, { name: “gandalf” }]
const noAragorn = users.filter(filterByName(‘aragorn’)); // [ { name: “bilbo” }, { name: “gandalf” }]
// example string concatenation
const makeGreeting = (name) => (greeting = “hello, “) => greeting + name;
const greetStephen = makeGreeting(“Stephen”);
console.log(greetStephen()); // “hello, Stephen”
console.log(greetStephen(“bye, “)); // “bye, Stephen”
// example onClick handler in React
const List = () => {
const handleClick = (item) => (event) => {
event.preventDefault();
console.log(item);
}
return (
{items.map((item,index) => (
<li
onClick={handleClick(item)} // onClick passes an event
>{item.name}</li>
))}
);
}
// example form in React
const Form = () => {
const [formData, setFormData] = useState({ name: “”, email: “” });
const handleOnChange = (name) => (event) => {
const { value } = event.target;
setFormData({ …formData, [name]: value });
}
return (
<form>
<input
name='name'
value={formData.name}
onChange={handleOnChange(“name”)}
></input>
<input
name='email'
value={formData.email}
onChange={handleOnChange(“email”)}
></input>
</form>
);
}
As you can see there are several use cases for “curried” functions. They don’t solve everything, but in the correct circumstances can be useful for reducing repeated code.