In this article, I want to describe a very simple concept used in functional programming: currying.
We have to imagine that in most functional languages, we deal mainly with functions. I will use Java as language for my examples, but it should be fairly easy to understand them and adapt to your language of choice.
Let's analyse the type of few functions:
public interface ExampleFunctions {
Integer invertSign(Integer input);
Integer add(Integer a, Integer b);
Command createFrom(Properties ps, Pattern p, CommandFactory f);
}
Here we can see we have:
-
invertSign
that has type:Function<Integer, Integer>
-
add
that has type:Function<Integer, Integer, Integer>
-
createFrom
that has type:Function<Properties, Pattern, CommandFactory, Command>
For non Java programmers, the interface Function
in Java is generic or parameterised and the first n parameters represent the function inputs or arguments, and the last is the return type.
Now let's focus our attention on the two functions that have more than one argument add
and createFrom
.
Suppose you want to create an adder object, that adds any integer to a given number, like a fiveAdder
.
Using an object oriented style, you can write a class like:
public class Adder {
private final Integer firstAddend;
public Adder(Integer firstAddend){
this.firstAddend = firstAddend;
}
public Integer add(Integer number){
return firstAddend + number;
}
}
In this class we require a firstAddend
integer dependency in the constructor, and then we have a different add
method that takes just one parameter.
This way of passing parameters to an object is called Dependency Injection
in this case through a constructor.
Using the concept of currying
, we could have done this without declaring any class.
Currying is the transformation of a function that takes a structure of n arguments, into a chain of functions each taking one argument.
So for functions that take two arguments, we can write a curry function like this:
Function<A, Function<B, C>> curry(Function<A, B, C> f);
Then using this function we could have written:
var fiveAdder = curry(ExampleFunctions::add).apply(5);
Just like that, we have a function that adds any integer to five.
Now take for example the other function createFrom
.
Imagine a situation where you have different Command
objects, like TurnOnCommand
and TurnOffCommand
, and you want to use that method to create them in different ways (using different patterns, and different constructors), but for all of them, you want to use the same Properties
object.
We can leverage the currying concept for functions with 3 arguments:
Function<A, Function<B, Function<C, D>>> curry(Function<A, B, C,D> f);
And then write:
var someProperties = ...;
var factory = curry(ExampleFunctions::createFrom).apply(someProperties);
var turnOnFactory = factory.apply(turnOnPattern).apply(TurnOnCommand::new);
var turnOffFactory = factory.apply(turnOffPattern).apply(TurnOffCommand::new);
There you have a factory
function variable created after applying the first argument, someProperties
, to the curried version of the createFrom
method.
Without using currying you could have written:
var someProperties = ...;
var turnOnFactory = createFrom(someProperties, turnOnPattern, TurnOnCommand::new);
var turnOffFactory = createFrom(someProperties, turnOffPattern,TurnOffCommand::new);
or using dependency injection:
public class CommandsFactory{
private final Properties p;
public CommandsFactory(Properties p){
this.p = p;
}
public Command createFrom(Pattern p, CommandFactory f){
...
}
}
I hope you now have clear what currying
means, and how you can use it as a replacement for new class creation and dependency injection, or how somehow these concepts are related.
Top comments (3)
Nice article! Yes, function currying may replace almost every redundant syntax constructions in the modern programming languages. I wish Java to have more elegant function declaration and composition syntaxes.
Well explained. I think showing the before/after in a language like Kotlin (or Scala) that has first-class functions would make the case even more strongly.
Yes Java is a bit verbose, but compared to when I started with Java 1.2 things got better for sure :)