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).

– 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.

– 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:

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. 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:

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. 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:

boolean isOldArthur = name.is("Arthur").and(age.is(i -> i >= 40)).test(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:

Stream<Record> oldArthurs = records.stream().filter(name.is("Arthur").and(age.is(i -> i >= 40))); 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.