Elements of functional programming

 

Elements of functional programming

Функциональный интерфейс

  • Интерфейс (не абстрактный класс), содержащий единственный абстрактный метод
  • Может быть аннотирован как @FunctionalInterface

Функциональное выражение в Java

  • Отображается на функциональный интерфейс
  • Не имеет состояния
  • Конкретный тип устанавливается по контексту
  • Либо лямбда-выражение ((a, b) -> a+b) либо ссылка на метод (Integer::sum)
  • Не гарантирует identity
  • Не гарантирует getClass() identity

Лямбда выражение

  • Аргументы:
    • (Type1 name1, Type2 name2)
    • (name1, name2)
    • Name
  • Стрелочка ->
  • Тело:
    • Выражение System.out.println()
    • Блок { System.out.println(); }
  • Void-compatible (void SAM-single abstract method):
    • Блок: каждый return не содержит выражения
    • Выражение: допустимое в утверждении (вызов метода, присваивание, печать и т.д.)
  • Value-compatible (non-void SAM-single abstract method):
    • Блок: каждый return содержит выражение и нормальное завершение невозможно
    • Выражение: имеет значение не void

Примеры лямбда-выражений

// Пример лямбды
public class Main {
    public static void main(String[] args) {
        Runnable r = () -> System.out.println("Hello");
        r.run();
        Runnable rn = (args.length > 0 ?
                () -> System.out.println("Hello " + args[0] + "!") :
                () -> System.out.println("Hello World!"));
    }
}
// Пример лямбды
public class Main {
    static void run(Runnable r) {
        r.run();
    }

    public static void main(String[] args) {
        run(() -> System.out.println("Hello world!"));
    }
}
// Пример лямбды
public class Main {
    public static void main(String[] args) {
        Object x = (Runnable) (() -> System.out.println("Hello!"));
    }
}
class Main {
    public static void main(String[] args) {
        IntSupplier x = () -> 5;
        IntSupplier x2 = () -> System.out.println(5); // Error. Value-compatible
        
        Runnable y1 = () -> 5; // Error. Void-compatible
        Runnable y2 = ()-> System.out.println(5);
    }
}

Захват (Capture) значений

  • Локальная переменная - должна быть effectively final и инициализирована к месту использования лямбды, берется значение этой переменной.
  • Поле текущего класса - захватывается this, значение из поля читается при выполнении.
  • Статическое поле - ничего не захватывается, значение читается при выполнении.
  • Closure (Замыкание) - лямбда-выражение с захваченными переменными.

Примеры захвата значений (замыканий)

class Main {
    int field = 10;
    static int sField = 15;
    
    void test() {
        int var = 5;
        Rnnable r1 = () -> System.out.println(var);
        Rnnable r2 = () -> System.out.println(field);
        Rnnable r3 = () -> System.out.println(sField);
        field = 5;
        sField = 5;
        r1.run(); // 5
        r2.run(); // 5
        r3.run(); // 5
    }
}

Ссылка на метод (method reference)

  • Статический метод:
    • IntBinaryOperator sum = Integer::sum;
  • Не статический метод (первый параметр SAM-метода превращается в квалификатор):
    • Function<String, String> trimmer = String::trim; // res = trimmer.apply(" hello ");
  • Не статический метод привязанный к экземпляру(instance-bound):
    • Predicate<String> isFoo = "foo"::equals;
    • Consumer<Object> printer = System.out::println;
  • Конструктор:
    • Supplier<List<String>> listFactory = ArrayList::new;
  • Конструирование массива:
    • IntFunction<String[]> arrayFactory = String[]::new;

Пример

class Main {
    public static void main(String[] args) {
        Predicate<String> pred = "Java"::equals;
        System.out.println(pred.test("Java"));      // true
        System.out.println(pred.test("Kotlin"));    // false
        
        Consumer<Object> methodRefPrinter = System.out::println;
        Consumer<Object> lambdaPrinter = obj -> System.out.println(obj);
        System.setOut(null);
        methodRefPrinter.accept("hello");   // hello
        lambdaPrinter.accept("hello");      // Error. NullPointerException
    }
}

Примитивы функционального программирования

Примеры

import java.util.Objects;
import java.util.function.BiFunction;

class Main {
    // Привязка аргумента (bind)
    static <A, B, C> Function<B, C> bind(BiFunction<A, B, C> fn, A a) {
        Objects.requireNonNull(fn);
        return b -> fn.apply(a, b);
    }
    
    // Карирование (curry)
    static <A, B, C> Function<A, Function<B, C>> curry(Bifunction<A, B, C> fn) {
        return a -> b -> fn.apply(a, b);
    }

    public static void main(String[] args) {
        Function<Integer, Integer> inc = bind(Integer::sum, 1);
        System.out.println(inc.apply(10));  // 11

        System.out.println(curry(Integer::sum).apply(5).apply(6));  // 11
    }
}

Optional< T >

  • Либо значение (не null), либо его отсутствие
  • Optional.of(obj) создает Optional с переданным значением
  • Optional.ofNullable(obj) если значение не null - создается Optional со значением,
    если значение null - создается пустой Optional
  • Optional.empty() создает пустой Optional

Пример:

class Main {
    static Optional<Integer> toInteger(String opt ){
        try {
            return Optional.of(Integer.valueOf(opt));
        } catch (NumberFormatException ex) {
            return Optional.empty();
        }
    }
}

Методы Optional (не все, часто используемые)

  • filter(Predicate T)
    • boolean isFooEqualsBar = Optional.of("foo").filter("bar"::equals).isPresent();
  • <U> map(Function<T, U>)
    • String trimmed = Optional.of(" foo ").map(String::trim).get();
  • <U> flatMap(Function<T, Optional<U>>)
    • Integer num = Optional.of("1234").flatMap(x -> toInteger(x)).orElse(-1);
  • orElseGet(Supplier<T>)
    • Double random = Optional.<Double>empty().orElseGet(Math::random);
  • <X extends Throwable> orElseThrow(Supplier<T>) throws X
    • Double random = Optional.<Double>empty().orElseThrow(Exception::new);
  • or(Supplier<? extends Optional<? extends T>>)
    • getOneOptional().or(() -> getAnotherOptional());

Пример:

interface User {
    String name();
    boolean isActive();
    void updateCarma(int delta);
}
interface UserRepository {
    Optional<User> findUser(String name);
}

class Main {
    void increaseUserCarma(UserRepository repository, String name) {
        repository.findUser(name)
                .filter(User::isActive)
                .orElseThrow(() -> new IllegalArgumentException("No such user: " + name))
                .updateCarma(1);
    }
}