An Extractor<S, T> may be seen as a partial function from S to T: it can only “extract” a value of type T from a value of type S if such a value (or the material from which one can be created) is present. In Octarine, an Extractor<T, V> is a cross between a Function<T, Optional<V>> and a Predicate<T> . It has three methods:
- V extract(T source) – extracts a value of type V directly from the source (or fails with an exception).
- Optional<V> apply(T source) – returns either a value of type V extracted from the source and wrapped in an Optional, or Optional.empty if no such value is available.
- boolean test(T source) – returns true if the source contains the kind of value that we want, and false otherwise.
The obvious example is a Record , which might or might not contain a value for a given Key<T> . We have:
1 2 3 4 5 6 7 8 9 10 11 |
Key<Integer> age = Key.named("age"); Record recordWithAge = Record.of(age.of(23)); age.extract(recordWithAge); // returns 23. age.apply(recordWithAge); // returns Optional.of(23). age.test(recordWithAge); // returns true. Record emptyRecord = Record.empty(); age.extract(emptyRecord); // throws an exception age.apply(emptyRecord); // returns Optional.empty(). age.test(emptyRecord); // return false. |
We can enhance extractors with predicates to look for values matching additional criteria besides existence – for example:
1 2 3 4 5 6 7 8 9 10 11 |
Key<Integer> age = Key.named("age"); Extractor<Record, Integer> ageOverForty = age.is(i -> i >= 40); Extractor<Record, Integer> ageUnderForty = age.is(i -> i < 40); Record record = Record.of(age.of(23)); ageOverForty.extract(record); // throws an exception ageUnderForty.extract(record); // returns 23 ageOverForty.apply(record); // returns Optional.empty() ageUnderForty.apply(record); // returns Optional.of(23) ageOverForty.test(record); // returns false. ageUnderForty.test(record); // returns true. |
This makes them useful when composing tests on a record:
1 |
boolean isOldArthur = name.is("Arthur").and(age.is(i -> i >= 40)).test(record)); |
Which in turn makes them useful when filtering a collection of records:
1 |
Stream<Record> oldArthurs = records.stream().filter(name.is("Arthur").and(age.is(i -> i >= 40))); |
Any OptionalLens<T, V> in Octarine is also an Extractor<T, V> , and any plain old Lens<T, V> can be turned into an Extractor<T, V> by calling Lens::asOptional on it.