Monday, December 10, 2007

An unrestricted closure example

An interesting question was raised recently about whether there are realistic examples of needing to pass an unrestricted closure when not using the control invocation syntax.

Let's say you were tasked with writing a method which calculates the returns on an accumulator bet. An accumulator (also known as a Parlay in many parts of the world) is a bet where you select several teams/opponents/whatever to win their matches. Your stake (the amount of money you are betting) goes onto the first of those selections, and if your prediction was correct, the returns from that part of the bet go onto the second of the selections, and so on. If any of your selections lost, you lose your money.

As an example, I could have a £10 accumulator bet on the following teams to win in the English Premiership this weekend:

Arsenal @ 2.40
Liverpool @ 2.60
Blackburn @ 2.25
Bolton @ 4.20
Everton @ 2.70

The number after the @ represents the odds for that team to win - 2.0 would mean doubling my money. If all these teams win their matches, I get £10 * 2.4 * 2.6 * 2.25 * 4.2 * 2.7 = £1592.14 back. If any one of these teams lose, I get nothing back. If any of the matches involved do not end in a decisive win/lose result (eg. a match is cancelled), that part of the bet is 'void' - I don't lose my whole bet, but it's calculated as though the odds on that selection were 1.0 (neither profit nor loss).

How could we write this?

Imagine we have a method called reduce(), which is defined as follows:

<E, R> R reduce(Iterable<E> values, R initial, {R, E => R} reducer) {
R result = initial;
for (E element : values) {
result = reducer.invoke(result, element);
}
return result;
}

This method iterates over values, and invokes reducer passing it the result of the invocation it performed on the previous element, and the current element. In the case of the first iteration, there was no previous element, so it uses the value provided as initial. The result of the very last invocation is what's returned from reduce().

Here's a silly but easy example, before I get back to the main point:

List<String> words = Arrays.asList("Mary", "had", "a", "little", "lamb");
// sum the number of characters in the words
int totalChars = reduce(words, 0, {Integer count, String word => count + word.length()});

OK, so we can also use reduce() to help us calculate the returns on an accumulator bet:

BigDecimal calculateReturns(AccumulatorBet bet) {

List<Selection> selections = bet.getSelections();

return reduce(selections, bet.getStake(),
{BigDecimal money, Selection selection =>
Result result = loadResult(selection);
if (result.isWinner()) {
money = money.multiply(selection.getOdds());
}
else if (result.isLoser()) {
money = BigDecimal.ZERO;
}
money
});
}

If you think about it though, once you hit a loser, there's no point continuing - the returns (value held in money) are now zero, and no amount of multiplication is going to change that. You might be tempted to just change:

else if (result.isLoser()) {
money = BigDecimal.ZERO;
}

to

else if (result.isLoser()) {
return BigDecimal.ZERO;
}

which would return BigDecimal.ZERO from calculateReturns(). At the very least, it would save you from unnecessary calls to loadResult(), which could well be a costly operation.

That's only possible if we're allowed to pass an unrestricted closure / function type to reduce(), which is not a method with which we can use the control invocation syntax. The current (v0.5) specification does allow this, but there's debate over whether it's a useful feature (see the comments on that page).

Real betting systems have to settle much more complicated bets, and have to do so as quickly as possible. Shortcuts like the above are very useful for achieving that.

1 comment:

Neal Gafter said...

You can implement a type switch using BGGA, but not with the control invocation syntax. Therefore, if you need to return early, you're out of luck.

Another classic problem that is much more convenient with nonlocal returns is this: you're given an Object[] that contains other Object[]s and Integers, recursively. Return the sum of all the values, or 0 if there is a 0 anywhere in the data.