TL;DR Java version of sequence and traverse functions in "Scala Functional Design & Programming" Scala Functional Design & Programming-A Thorough Guide to Functional by Scala Contributor
If you receive a list of ʻOptional and all have values (= isPresent is true), you want to make a list of values without ʻOptional
.
If at least one value is null
(= isPresent is false), I want to return ʻOptional.empty`.
Roughly, I think you can write it like this. If null is included in the argument or list element, an error will occur, but it is not considered here. (It's worse to use ~~ null ~~)
public static <T> Optional<List<T>> sequence(List<Optional<T>> xs) {
List<T> ys = new ArrayList<>();
for (Optional<T> x : xs) {
//If there is an element whose value does not exist, the process is terminated at that point.
if(x.isEmpty()) {
return Optional.empty();
}
//If the value exists, unwrap Optional and add it to the list
ys.add(x.get());
}
//Wrap the list with only values with Optional and return it
return Optional.of(ys);
}
List<Optional<String>> xs = List.of(Optional.of("foo"), Optional.of("bar"));
List<Optional<Integer>> ys = List.of(Optional.of(1), Optional.empty(), Optional.of(100));
List<Optional<Boolean>> zs = List.of(); //Empty list
System.out.println(sequence(xs)); // Optional[[foo, bar]]
System.out.println(sequence(ys)); // Optional.empty
System.out.println(sequence(zs)); // Optional[[]]
It seems to work properly.
Suppose you have the following function that converts a string to a number:
If the target string can be converted to a number, this function will wrap the number in ʻOptional and return it. If the conversion fails, ʻOptional.empty
is returned. [^ 1]
public static Optional<Integer> safeParseInt(String s) {
try {
Integer n = Integer.valueOf(s);
return Optional.of(n);
} catch (NumberFormatException e) {
return Optional.empty();
}
}
Use this function to convert a list of String
s to a list of ʻOptional . Furthermore, if the element contains ʻOptional.empty
, as when applying the sequence function, consider a function that also makes the overall result ʻOptional.empty. Name it
traverse`.
//This is all convertible to numbers so Optional[[1, 100, 30]]Want to
Optional<List<Integer>> xs = traverse(List.of("1", "100", "30"));
//This is optional because it contains some values that cannot be converted to numbers..I want to make it empty
Optional<List<Integer>> ys = traverse(List.of("1", "3", "hoge", "0", ""));
This could be implemented as follows using the sequence function above.
public static Optional<List<Integer>> traverse(List<String> xs) {
//Once Optional<Integer>Convert to a list of
List<Optional<Integer>> ys = new ArrayList<>();
for (String x : xs) {
Optional<Integer> o = safeParseInt(x);
ys.add(o);
}
//List with sequence function<Optional<Integer>>Optional<List<Integer>>Conversion to
return sequence(ys);
}
If you move it, you will see that you are getting the desired result.
System.out.println(traverse(List.of("1", "100", "30"))); // Optional[[1, 100, 30]]
System.out.println(traverse(List.of("1", "3", "hoge", "0", ""))); // Optional.empty
However, the above is a little wasteful writing. See the following example. The travase function is inlined and output based on the intermediate results.
List<String> xs = List.of("1", "hoge", "2", "3", "4", "5", "6", "7", "8", "9");
//This is exactly the same process as the travarse function
List<Optional<Integer>> ys = new ArrayList<>();
for (String x : xs) {
Optional<Integer> o = safeParseInt(x);
ys.add(o);
}
Optional<List<Integer>> zs = sequence(ys);
//Output intermediate list and results
System.out.println(ys); // [Optional[1], Optional.empty, Optional[2], Optional[3], Optional[4], Optional[5], Optional[6], Optional[7], Optional[8], Optional[9]]
System.out.println(zs); // Optional.empty
The fact that zs
becomes ʻOptional.emptyis as expected. However, this has generated an intermediate list
ys with the same length as
xs. It is a decision to apply the safeParseInt function to the second element
" hoge " of
xs, and when ʻOptional.empty
is completed, the overall result will also be ʻOptional.empty. It feels a bit inefficient to continue the conversion process after knowing the result. If the number of elements is small, it will not be a problem, but if the number of elements is large, it may affect the performance. When ʻOptional.empty
is reached, the safeParseInt function is not applied to the elements after that, and I want to return ʻOptional.empty` immediately.
Rewrite the travarse function so that it does.
public static Optional<List<Integer>> traverse(List<String> xs) {
List<Integer> ys = new ArrayList<>();
for (String x : xs) {
//Convert strings to numbers
Optional<Integer> o = safeParseInt(x);
//If the conversion fails, the process will be terminated at that point.
if(o.isEmpty()) {
return Optional.empty();
}
//If the conversion is successful, remove the optional wrap and add it to the list.
ys.add(o.get());
}
//Wrap the list with only numbers with Optional and return it
return Optional.of(ys);
}
I've stopped using the sequence function and are doing both conversion and branching within a single loop.
Now, if the conversion fails in the middle, ʻOptional.empty will be returned immediately at that point. Make safeParseInt a parameter so that you can use any function that can be converted to ʻOptional
.
public static <T, R> Optional<List<R>> traverse(List<T> xs, Function<T, Optional<R>> f) {
List<R> ys = new ArrayList<>();
for (T x : xs) {
Optional<R> o = f.apply(x);
if(o.isEmpty()) {
return Optional.empty();
}
ys.add(o.get());
}
return Optional.of(ys);
}
When using it, pass it as a lambda expression or method reference. In summary, it looks like this:
Main.java
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
//Lambda expression
Optional<List<Integer>> xs = traverse(List.of("1", "100", "30"), x -> safeParseInt(x));
//Method reference
Optional<List<Integer>> ys = traverse(List.of("1", "3", "hoge", "0"), Main::safeParseInt);
System.out.println(xs); // Optional[[1, 100, 30]]
System.out.println(ys); // Optional.empty
}
public static Optional<Integer> safeParseInt(String s) {
try {
Integer n = Integer.valueOf(s);
return Optional.of(n);
} catch (NumberFormatException e) {
return Optional.empty();
}
}
public static <T, R> Optional<List<R>> traverse(List<T> xs, Function<T, Optional<R>> f) {
List<R> ys = new ArrayList<>();
for (T x : xs) {
Optional<R> o = f.apply(x);
if(o.isEmpty()) {
return Optional.empty();
}
ys.add(o.get());
}
return Optional.of(ys);
}
//No longer used
// public static <T> Optional<List<T>> sequence(List<Optional<T>> xs) {
// List<T> ys = new ArrayList<>();
// for (Optional<T> x : xs) {
// if(x.isEmpty()) {
// return Optional.empty();
// }
// ys.add(x.get());
// }
// return Optional.of(ys);
// }
}
I stopped calling the sequence function from the travarse function, but in fact I can use this new travarse function to reimplement the sequence function as follows:
public static <T> Optional<List<T>> sequence(List<Optional<T>> xs) {
return traverse(xs, Function.identity()); // traverse(xs, x -> x)But yes
}
It may be a little strange, but here Function.identity ()
(or x-> x
[^ 2]) is a function that takes ʻOptional and returns ʻOptional <T>
. , Travarse's second argument Function <T, Optional <R >> f
can be written as above, as long as the return type is ʻOptional`. [^ 3]
For the sake of explanation, let's match the type parameters of the travarse function to this new sequence function.
public static <T> Optional<List<T>> traverse(List<Optional<T>> xs, Function<Optional<T>, Optional<T>> f) {
List<T> ys = new ArrayList<>();
for (Optional<T> x : xs) {
Optional<T> o = f.apply(x); //f is Function#Since it is identity, x and o are the same instance
if(o.isEmpty()) {
return Optional.empty();
}
ys.add(o.get()); //After all, it's the same as packing the contents of x
}
return Optional.of(ys);
}
As with the sequence function defined at the beginning, you can see that we just removed the Optional of each element of List <Optional <T >>
.
I don't think Java defines general-purpose functions that take these functions as arguments (it's hard to standardize because there are many functional interfaces), but I thought of it somehow, so I wrote it. Aside from its practicality, it is recommended because it is fun to be a brain teaser.
Thank you for reading for me until the end. If you have any questions or deficiencies, please contact us in the comments section or Twitter.
[^ 1]: Optional x-> x
, but this way of writing will generate an object of typeFunction <T, T>
every time, so unless you have a specific reason I think it's better to use Function.identity ()
.
[^ 3]: It may be confusing because it is covered by the type parameter T
, but what the travarse T
and sequence T
point to here is a different type. T
in travarse corresponds to ʻOptional in sequence. Since the type parameters
T, R of travarse can be the same type, the
Function <Optional
Recommended Posts