Getting started
Installation
Project status
Basic concepts
With Apex, we have to write imperative code in loops to transform data.
List<Opportunity> opportunities = [SELECT ...];
List<Opportunity> largeOpportunities = new List<Opportunity>();
for (Opportunity opp : opportunities) {
if (opp.Amount > 10000) {
largeOpportunities.add(opp);
}
}
Apex FP implements common operations on SObject
instances, and allows functional (declarative) transformation of data. Here’s how filtering looks like with Apex FP:
List<Opportunity> opportunities = [SELECT ...];
List<Opportunity> largeOpportunities = SObjectCollection.of(opportunities).filter(Fn.Match.field(Opportunity.Amount).greaterThan(10000)).asList();
To enable functional programming, Apex FP provides three things:
- Function interfaces
- Function factories
- Higher order functions
Function interfaces
First, Apex FP defines a reasonable set of Function interfaces for functions. Unlike most modern languages, Apex does not support functions as "first class citizens". Apex FP therefore provides interfaces for common classes of functions out of the box, like "functions that take an SObject
and return a Boolean
":
public interface SObjectPredicate {
Boolean call(SObject record);
}
or "functions that take an SObject
and return an SObject
":
public interface SObjectToSObjectFunction {
SObject call(SObject record);
}
Function factories
Second, because we cannot define anonymous classes or functions in Apex, Apex FP provides function factories that can create common functions which respect Apex FP’s Function interfaces. For example, Match
can be used to build a function which tests if a record matches some criteria.
SObjectPredicate isNameFoo = Fn.Match.field(Opportunity.Name).equals('Foo');
Higher order functions
Finally, Apex FP provides classes with higher order functions as methods. Higher order functions are functions that take functions as arguments and use them to transform data.
The majority of higher order functions are provided by two main classes SObjectCollection
, and SObjectStream
.
For example, they both have a filter
method. It accepts a SObjectPredicate
function instance and uses it to test whether records satisfy the predicate, and keeps just the ones that do. filter
is therefore a higher order function.
public class SObjectCollection {
public SObjectCollection filter(SObjectPredicate predicate)
}
Another example is mapAll
which accepts an SObjectToSObjectFunction
instance and applies it to records in a collection to get new records.
public class SObjectCollection {
public SObjectCollection mapAll(SObjectToSObjectFunction fn)
}
You can use Apex FP’s function factories, write your own functions or mix and match.
SObjectCollection
transforms data eagerly, while SObjectStream
does so lazily. What’s the difference?
Imagine we’re looking for a first opportunity that has a large amount. We could use filter
to filter those opportunities that have a large amount and then take the first one.
SObjectCollection
would first check the entire list of opportunities to find all opportunties that are larger than a certain amount, and then take the first one out of them. SObjectCollection
is more convenient to use, but it might not be as performant for large datasets if we are looking for one or a couple of elements from the filtered collection. We can also use it as many times as we like.
With SObjectStream
, we iterate through the stream to check one opportunity at a time, and as soon as we find a large opportunity, we can stop checking. SObjectStream
requires some additional effort to iterate through records. We can also only use it once. After it’s consumed, we have to recreate it.
Putting it all together
With Function interfaces, function factories and a nice set of classes for working with SObject
collections with higher order functions , we finally get to data transformation expressions like:
List<Opportunity> largeOpportunities = SObjectCollection.of(opportunities).filter(Fn.Match.field(Opportunity.Amount).greaterThan(10000)).asList();