Using Lazy Evaluation to Write Salesforce Apex Code Without For-Loops
by Nebula Consulting
1st April 2019
Development
Functions or for-loops?
A developer working in Javascript has access to some really convenient functions on Arrays. Functions like filter() and map() can avoid the need to write for-loops. Is this actually a good thing? Can we write Apex this way?
Let’s try it with for-loops
Suppose we want to write a trigger on Contact which runs when a Birthdate is added. The trigger should create a task for the next birthday. We could write this with a conventional for-loop (NB, the code below is using the trigger framework from Salesforce Trigger Handlers Driven by Custom Metadata. Hopefully, the intent is clear without necessarily knowing the trigger framework):
public without sharing class ContactBirthdayTasks implements AfterUpdate {
private static final Date TODAY = Date.today();
public void handleAfterUpdate(List oldList, List newList) {
List happyBirthdayTasks = new List();
for(Integer i=0; i < newList.size(); i++) {
Contact newContact = newList[i];
if(newContact.Birthdate != null && oldList[i].Birthdate == null) {
Date activityDate = Date.newInstance(TODAY.year(), newContact.Birthdate.month(), newContact.Birthdate.day());
if(activityDate < TODAY) {
activityDate = activityDate.addYears(1);
}
happyBirthdayTasks.add(new Task(
WhoId = newContact.Id,
Subject = 'Happy Birthday',
ActivityDate = activityDate
));
}
}
insert happyBirthdayTasks;
}
}
Let’s try it lazy
How does it look if we write it in terms of filter() and map()? (NB, we have to call it mapValues() in Apex because map is a reserved word)
public without sharing class ContactBirthdayTasksLazy implements AfterUpdate {
public void handleAfterUpdate(List oldList, List newList) {
insert (List)new LazyTriggerContextPairIterator(oldList, newList)
.filter(new BirthdayChangedFromNull())
.mapValues(new TriggerContextPair.NewRecordFromPair())
.mapValues(new BirthdayTaskFromContact())
.toList(new List());
}
private class BirthdayChangedFromNull extends TriggerContextBooleanFunction {
public override Boolean isTrueFor(SObject oldRecord, SObject newRecord) {
Contact newContact = (Contact)newRecord;
return newContact.Birthdate != null && ((Contact)oldRecord).Birthdate == null;
}
}
private class BirthdayTaskFromContact implements Function {
private final Date TODAY = Date.today();
public Object call(Object o) {
Contact newContact = (Contact)o;
return new Task(
WhoId = newContact.Id,
Subject = 'Happy Birthday',
ActivityDate = getActivityDate(newContact.Birthdate)
);
}
private Date getActivityDate(Date birthdate) {
Date activityDate = Date.newInstance(TODAY.year(), birthdate.month(), birthdate.day());
if(activityDate < TODAY) {
activityDate = activityDate.addYears(1);
}
return activityDate;
}
}
}
This version has many more lines of code (to be fair, many are blank or just braces), but is has split out the responsibilities nicely. First, let’s consider what filter() and mapValues() do:
filter() picks out selected items from a collection by calling a function on each, to see if it should be included
mapValues() transforms one collection into another collection of the same size, by calling a given function on each element
Once you are used to reading this sort of code, you can tell what the trigger does by reading handleAfterUpdate() as if it is written in plain english:
From the trigger context, filter to just the old/new pairs where the Birthdate has changed from null; pick the new records from the trigger context; create a birthday Task from each new Contact; finally, turn that into a List of Tasks.
This code satisfies a number of the criteria of clean code (see https://www.amazon.co.uk/dp/0132350882/). Everything in handleAfterUpdate() is done at the same level of abstraction. There is no loop counter getting in the way of reading the high-level intent. Furthermore, each of the components does exactly what it says in the name, and nothing more. Everything has a single responsibility. And since everything has a name and a single responsibility, we don’t need any comments to explain ourselves.
I’m not saying that for-loops are bad, but using filter() and mapValues() is certainly cleaner when they are a good fit for the problem at-hand.
So what’s lazy about all this?
The “lazy” in the title of this article refers to lazy evaluation. It’s a key concept in functional programming languages, but it can also be implemented in other languages. If you’re new to it, I’d highly recommend the following video:
Ironically for a talk about laziness, the delivery in that video is so fast that you might need to take the odd break as you watch it.
Understanding the power of lazy evaluation
We can understand the power of lazy evaluation by considering an eager implementation of filter() and mapValues() . Suppose filter() and mapValue() return lists, then we could implement them as something like this:
public static List
mapValues() is implemented as a subclass of LazyIterator where calling next() is no longer just delegated. Now, it calls the mappingFunction on the result that it gets from its internal iterator:
public class LazyMapIterator extends LazyIterator {
private Function mappingFunction;
public LazyMapIterator(Iterator iterator, Function mappingFunction) {
super(iterator);
this.mappingFunction = mappingFunction;
}
public override Object next() {
return (Object)mappingFunction.call(iterator.next());
}
}
filter() is also implemented with a subclass of LazyIterator . This time, we override both next() and hasNext() . Now the iterator operations test the potential next value against matchingFunction by peeking ahead by one value:
Conceptually, that's it. There are some other classes in there to suit particular scenarios e.g. facilitating trigger handlers by iterating over pairs with the new/old records. But these just add convenience to the core idea.
Why didn't you use Callable?
The Function interface used in these classes looks a bit like the standard Callable interface provided by Salesforce. One of the reasons that Callable exists is that you don't need any dependencies to use it - it is included in the Salesforce system. At Nebula, Function is included in a common library that we install for all customers anyway, so it is likewise always available.
Also, in my opinion, Callable.call(String action, Map<String, Object> args) has too many parameters. If you're going to type everything loosely, why split out the action parameter? It's implying a usage where the class implementing Callable has many functions that can be accessed by calling call('functionOne', ...) or call('functionTwo', ...) . I prefer a design where each class implementing Function does just one thing.
What about Apex Lambda?
There is an existing Github repo called Apex Lambda which has similar aims. It's great but, in its initial form, it did not support lazy evaluation. It's moving in that direction, but is still oriented around Lists rather than Iterators. We also like our own version because some of the interfaces mentioned here are used for other functions in our library.
What about all that casting?
There is some ugliness involved because we can't write our own templated types in Apex. For example:
Empowering our customers to succeed is one of our core business values and we really do put customer success at the heart of everything we do. To see how we can help you achieve your business objectives and realise the power of the Salesforce platform, get in touch with our team of experts.