|
Composing predicates
Another problem with our email filters is that we can currently only pass a single EmailFilter to our newMailsForUser function. Certainly, our users want to configure multiple criteria. We need a way to create a composite predicate that returns true if either any, none or all of the predicates it consists of return true.
Here is one way to implement these functions:
1
2
3
4
def any[A](predicates: (A => Boolean)*): A => Boolean =
a => predicates.exists(pred => pred(a))
def none[A](predicates: (A => Boolean)*) = complement(any(predicates: _*))
def every[A](predicates: (A => Boolean)*) = none(predicates.view.map(complement(_)): _*)
The any function returns a new predicate that, when called with an input a, checks if at least one of its predicates holds true for the value a. Our none function simply returns the complement of the predicate returned by any – if at least one predicate holds true, the condition for none is not satisfied. Finally, our every function works by checking that none of the complements to the predicates passed to it holds true.
We can now use this to create a composite EmailFilter that represents the user’s configuration:
1
2
3
4
5
val filter: EmailFilter = every(
notSentByAnyOf(Set("johndoe@example.com")),
minimumSize(100),
maximumSize(10000)
)
Composing a transformation pipeline
As another example of function composition, consider our example scenario again. As a freemail provider, we want not only to allow user’s to configure their email filter, but also do some processing on emails sent by our users. These are simple functions Email => Email. Some possible transformations are the following:
1
2
3
4
5
6
7
8
9
val addMissingSubject = (email: Email) =>
if (email.subject.isEmpty) email.copy(subject = "No subject")
else email
val checkSpelling = (email: Email) =>
email.copy(text = email.text.replaceAll("your", "you're"))
val removeInappropriateLanguage = (email: Email) =>
email.copy(text = email.text.replaceAll("dynamic typing", "**CENSORED**"))
val addAdvertismentToFooter = (email: Email) =>
email.copy(text = email.text + "\nThis mail sent via Super Awesome Free Mail")
Now, depending on the weather and the mood of our boss, we can configure our pipeline as required, either by multiple andThen calls, or, having the same effect, by using the chain method defined on the Function companion object:
1
2
3
4
5
val pipeline = Function.chain(Seq(
addMissingSubject,
checkSpelling,
removeInappropriateLanguage,
addAdvertismentToFooter))
|
|