FP / Funkcionálne programovanie v Jave

Funkcionálne programovanie
v Jave

Woman hands pouring latte in glass mug

Funkcie vyššieho rádu

Príklad

class LengthComparator implements Comparator<String> {
    public int compare(String x, String y) {
        return Integer.compare(x.length(), y.length());
    }
}

Arrays.sort(strings, new LengthComparator());

Anonymné triedy

Arrays.sort(strings, new Comparator<String>() {
    public int compare(String x, String y) {
        return Integer.compare(x.length(), y.length());
    }
});

Java 8

Anonymné funkcie

(x, y) -> x + y
Arrays.sort(strings,
    (x, y) -> Integer.compare(x.length(), y.length()));
Arrays.sort(strings, new Comparator<String>() {
    public int compare(String x, String y) {
        return Integer.compare(x.length(), y.length());
    }
});

Funkcionálne rozhrania

Comparator<String> byLength =
    (x, y) -> Integer.compare(x.length(), y.length());

Štandardné funkcionálne rozhrania — java.util.function

Aj vlastné

@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
System.out.println(add.calculate(5, 3));      // 8
System.out.println(multiply.calculate(5, 3)); // 15

Referencie na metódy

Arrays.sort(strings, (x, y) -> x.compareToIgnoreCase(y));
Arrays.sort(strings, String::compareToIgnoreCase);

Cieľový objekt je prvým argumentom.

Kompozícia funkcií

boolean allOdd(List<String> words) {
    Function<Integer, Boolean> odd = (n) -> n % 2 == 1;
    return words.stream().map(odd.compose(String::length))
                         .reduce(Boolean::logicalAnd)
                         .get();
}

Closure

Voľné premenné


public static void repeatMessage(String text, int count) {
   Runnable r = () -> {
      for (int i = 0; i < count; i++) {
         System.out.println(text);
         Thread.yield();
      }};
   new Thread(r).start();
}

Uzáver (closure)

Stream

Prúdy

Prúd (Stream) — štruktúra reprezentujúca postupnosť operácií nad kolekciou. Balík java.util.stream

List<Entry> topNoSql = entries.stream()
    .filter(a -> a.getTags().contains("nosql"))
    .sorted(Comparator.comparing(Entry::getWords).reversed())
    .limit(3)
    .collect(Collectors.toList());
int wordCount = entries.stream()
    .filter(a -> a.getTags().contains("nosql"))
    .map(Entry::getWords)
    .reduce(Integer::sum)
    .orElse(0);
int wordCount = entries.stream()
    .filter(a -> a.getTags().contains("nosql"))
    .mapToInt(Entry::getWords)
    .sum();

Paralelné prúdy

int wordCount = entries.parallelStream()
    .filter(a -> a.getTags().contains("nosql"))
    .mapToInt(Entry::getWords)
    .sum();

Lazy evaluation

take 25 (map (^2) [1..])
IntStream.iterate(1, i -> i+1)
    .map(i -> i*i)
    .limit(25).toArray()
boolean hasLongNoSql = entries.stream()
      .filter(a -> a.getTags().contains("nosql"))
      .map(Entry::getWords)
      .anyMatch(w -> w > 1000);

Maybe?

Optional

Ako vybrať hodnotu?

if (optional.isPresent()) {
    value = optional.get();
} else {
    value = defaultValue;
}

Alebo kratšie

value = optional.orElse(defaultValue);

Čo ak predvolenú hodnotu musím vypočítať?

if (optional.isPresent()) {
    value = optional.get();
} else {
    value = calculateDefaultValue(someParameters);
}

Tak?

value = optional.orElse(
    calculateDefaultValue(someParameters));

Alebo tak

value = optional.orElseGet(
    () -> calculateDefaultValue(someParameters));

Map

Optional<String> email = Optional.of("user@example.com");
Optional<Integer> domainLength = email
    .map(s -> s.substring(s.indexOf('@') + 1))  // extract domain
    .map(String::length);                       // get length
// Result: Optional[11] (length of "example.com")

Monády

Optional

public Optional<Integer> optionalAdd(
        Optional<Integer> val1, Optional<Integer> val2) {
    if (val1.isPresent() && val2.isPresent()) {
        return Optional.of(val1.get() + val2.get());
    }

    return Optional.empty();
}

flatMap

public Optional<Integer> optionalAdd(
        Optional<Integer> val1, Optional<Integer> val2) {
    return
           val1.flatMap( first ->
           val2.flatMap( second ->
           Optional.of(first + second)
    ));
}
studentStatus :: String -> Maybe String
studentStatus name =
    lookup name studentIDs
    >>= \ id -> lookup id statuses
    >>= \ status -> lookup readableStatus status
public Optional<String> studentStatus(String name) {
    return lookup(name, studentIDs)
        .flatMap(id -> lookup(id, statuses))
        .flatMap(status -> lookup(status, readableStatuses));
}