Lambdas (a.k.a. Blocks)
Lambdas are quickly becoming a common tool in the developer’s toolkit, offering an easy way to define simple, throwaway functions without the need to fully define them. In this article, let’s explore how lambdas are used in Guidewire PolicyCenter.
Base Code
Within Gosu scratchpad, add this to the top of the editor as this will be used throughout the tutorial. This tutorial needs to be performed in Guidewire PolicyCenter since we are talking about Ratebooks for many of the expressions
uses gw.api.database.Query
var ratebooks = Query.make(RateBook).select()
Something else to note is that almost all of the examples given in this tutorial, can be found within PC just by searching, if you wish to have the full context.
Statement List
A statement list is a simple function that returns a simple value. You will see that both versions of the function produce the same output.
var square = \ x : int -> x * x
the \ acts as the argument list to the function.
The -> acts as the contents of the function with an implicit return statement
The equivalent can be written as
square = function(x:int){
return x*x
}
Calling either method returns the same result
print(square(2))
Output: 4
Expression List
An expression list allows for more code due to the surrounding {}
Since this is now a code block, it requires a return statement.
var square = \ x : int -> { return x * x }
print(square(2))
Simple iterator
Here we are using a built in collection method to iterate over the collection.
Notice the ->. Anything within the {} acts as the function. In this case we have a nested function. Since we are not expecting a value (e.g. void) there is no need for a return statement.
ratebooks.each(\ratebook -> {
ratebook.RateTables.each(\rateTable -> {
print(rateTable.DisplayName)
})
})
This expression says to walk each ratebook and within each ratebook, walk each table, then print the name of the table.
Collection filtering
simple where
Here we use a statement list to perform a filter to only those in the Homeowners Line.
var filtered = ratebooks
.where(\ratebook -> ratebook.PolicyLine == 'HomeownersLine_HOE')
filtered.each(\ratebook -> {
print(ratebook.PolicyLine)
ratebook.RateTables.each(\rateTable -> {
print(rateTable.DisplayName)
})
})
As long as the logic is simple, a statement list can be used
_operand.CalcStep.Operands
.where(\o -> o.OperandOrder > 0 and (not mightBeBoolean(o, availLocalVars)))
.each(\o -> {o.LogicalNot = false})
since each of these statements returns the collection, they can be chained together.
w/ return
While this is a simple version of an expression list, it shows that we can begin to build more complex logic but then need the return statement.
var filteredwReturn = ratebooks.where(\ratebook -> {
var line = ratebook.PolicyLine
// we need to make this null safe in case it is for all lines
return line?.startsWith('Homeowners')
})
filteredwReturn.each(\ratebook -> {
print(ratebook.PolicyLine)
ratebook.RateTables.each(\rt -> {
print(rt.DisplayName)
})
})
Something to keep in mind with any lambda, but especially with nested ones, it is important to name your variable appropriately, vs the default value of elt. In this way you know what is going on.
This simply shows that the logic within the expression list, can become complex and have different return paths.
var accountContacts = accountContactRoleMap.Keys.where(\accountContact -> {
if (policyContactRoleFilter == null || !policyContactRoleFilter.HasElements) {
return true
}
var pcr = accountContactRoleMap.get(accountContact)
return policyContactRoleFilter.hasMatch(\role -> pcr*.Subtype.contains(role))
})
.sort()
.toTypedArray()
Notice, within the function block, we can introduce if statements and other complex return statements. Since these are lambdas, however, it is best to keep complexity to a minimum for performance reasons.
With index
Let’s say that we wanted to know the index of each item in a collection. We can use this block to achieve that. Notice the slight difference here in the signature. Remember the \ represents the argument list, meaning there are other arguments that can be part of the signature which the framework supplies values to. We have only been passing the iterator to the block but now we have the index as well. idx becomes the Index into the array. By placing your cursor inside the paren and pressing Ctrl-P you can find out the information about the arguments. Note: the order of the arguments is very important.
filtered.eachWithIndex(\ratebook, idx -> {
print("Ratebook${idx}=${ratebook.DisplayName}")
})
A more practical example might be the packing of an array. We need the index in order to properly reference the item for assignment. Note: index is zero-based
var pcPaymentPreviewItemsChanged = new PaymentPreviewItem[pcPaymentPreviewItems.length]
pcPaymentPreviewItems.eachWithIndex(\item, i -> { pcPaymentPreviewItemsChanged[i] = item })
With Key/Value
Many times, instead of a simple list of items, we use a map to have a stronger key. But then we need to iterate through finding both the key and its associated value.
var map = new HashMap<String, String>()
filtered.each(\rb -> {
rb.RateTables.eachWithIndex(\rt, idx -> {
map.put("RateTable-${idx}", rt.DisplayName)
})
})
map.eachKeyAndValue(\key, value -> {
print("${key}=${value}")
})
override function canEnrollUser(enrollmentData: ExternalUserEnrollmentRequestDTO_Ext): Authority[] {
enrollmentData.Details.eachKeyAndValue(\k, v -> {
_logger.logDebug("key: ${k}, value: ${v}")
})
}
Sorting/Ordering
filtered.sortByDescending(\rateBook -> rateBook.DisplayName).each(\elt -> {
print(elt.DisplayName)
})
There is some confusion when it comes to sorting around when to use sorting and when to use ordering. Guidewire has made great use of enhancements to allow these functions to exist for most enumerable types and are mostly interchangeable, however, typically ordering is done with lists and database queries while sorting can be done with arrays, collections and lists.
When you use lists, you can chain ordering with the function thenBy. This allows multi-level sorts.
.coveragePatternsForEntity(PersonalVehicle)
.orderBy(\coveragePattern -> coveragePattern.Code)
.thenBy(\coveragePattern -> coveragePattern.Priority)
Simple function pointer
In this example we want to allow a lambda (aka block) to be passed so we use the keyword block to achieve this. Here we are creating a variable which itself is a function.
gw.api.address.AddressFormatter
private var _filter : block(fieldId : AddressOwnerFieldId) : boolean
public function format(address : AddressFillable, delimiter : String) : String {
_filter = \ fieldId : AddressOwnerFieldId -> { return true }
return internalFormat(address, delimiter)
}
public function format(address : AddressFillable, delimiter : String, fields : Set<AddressOwnerFieldId>) : String {
_filter = \ fieldId : AddressOwnerFieldId -> { return fields.contains(fieldId) }
return internalFormat(address, delimiter)
}
What we are doing here is allowing the filtering mechanism to be changed while the internals remain the same. Within the internalFormat function, it will simply call the _filter function and use the result for further logic. This becomes very powerful in that we don’t need to understand what it does, only its result.
AddressCountrySettings.getSettings(_addrCountry).VisibleFields.contains(fieldId)
and _filter(fieldId)
Custom Mapper (for all code, refer to the edge package)
DefaultHoCoverablePlugin.gs
_additionalInterestUpdater = new ArrayUpdater<Dwelling_HOE, HODwellingAddlInt_HOE, AdditionalInterestDTO>(authzProvider) {
:ToCreateAndAdd = \dwelling, dto -> dwelling.addAdditionalInterestDetail(createAndUpdateContact(dto.PolicyAdditionalInterest)) as HODwellingAddlInt_HOE,
:ToRemove = \dwelling, e -> dwelling.removeFromAdditionalInterestDetails(e),
:ToAdd = \dwelling, e -> dwelling.addToAdditionalInterestDetails(e),
:DtoKey = \dto -> dto.FixedId,
:EntityKey = \e -> e.FixedId.Value
}
The array updater primarily relies on statement lists to allow for very generic list processing.
Breaking this down, we first see that the ArrayUpdater takes 3 types in its makeup.
class ArrayUpdater<C, E, DTO> extends UpdaterBase<C,E,DTO>
Context : C
Entity : E
DTO : DTO
In this way it can operate on any list because it makes heavy use of Generics.
Since it extends UpdaterBase, which takes an authorizer in its constructor, it instantiates the class with the 3 types passing in the authorizer.
new ArrayUpdater<Dwelling_HOE, HODwellingAddlInt_HOE, AdditionalInterestDTO>(authzProvider)
From there we will look at ToCreateAndAdd. This is a variable which takes a context and a dto. Note: the types of those variables were declared in the ArrayUpdater instantiation.
var _toCreateAndAdd(ctxt:C, dto:DTO):E as ToCreateAndAdd
Following our initial creation example, we know that ctxt will be of type Dwelling_HOE, dto will be of type AdditionalInterestDTO and the return type will be HODwellingAddlInt_HOE.
In order to use the updater, we need to look within the DefaultHoCoverablePlugin to find where it calls updateArray. It is here where the argument list is passed in.
_additionalInterestUpdater.updateArray(dwelling,additionalInterestCov,additionalInterestDTOs,\ item, dto ->{…contents left out for brevity}
Within the updateArray code, if you look at createAndAdd, we see that is calls into the base class’s createNewEntity.
private function createAndAdd(context: C, dto: DTO): E {
if ( ToCreateAndAdd == null ) {
var entityType = E
throw new InvalidParameterException("Creation of new instances of ${entityType} is not allowed")
}
var created = false
var entity = createNewEntity(context, dto, \ ctxt, d -> {
created = true
return ToCreateAndAdd(ctxt, d)
})
This method takes a context, a dto, and an anonymous function which will be called toCreate within the base class’s createNewEntity method. Notice the way createNewEntity is being called. The 3rd argument is a statementList which means it is an anonymous function. Since it doesn’t have a name, the base class gets to name it.
Looking further into the base class, UpdaterBase.createNewEntity, we can see that it eventually calls toCreate, passing the variables from the subclass and returning an entity.
protected function createNewEntity(ctxt:C,newDto:DTO, toCreate(ctxt:C,d:DTO):E):E {
var context = _creationContextProvider.CurrentContext
var tempID = findTempID(newDto)
var entity : E
if (tempID != null) {
entity = context.forKey<E>(tempID)
}
if ( entity == null ) {
entity = toCreate(ctxt,newDto)
if ( tempID != null ) {
_creationContextProvider.CurrentContext.add(tempID,entity)
}
}
return entity
}
With this final example, we see the power that lambdas can bring as it provides very generic definitions, leaving the actual implementation up to the consumer.
Summary
Lambdas are nothing new, but knowing their powerful generalizations, we can add lots of customization when needed. Using lambdas also cuts down on the boilerplate code needed to create a function, name the function, call the function and so on.
Until next time, Happy coding
Troy Stauffer
Senior Software Architect
Watch or read our other posts at Kimputing Blogs. You’ll find everything from Automated testing to CenterTest, Guidewire knowledge to general interest. We’re trying to help share our knowledge from decades of experience.