Рубрики
Без рубрики

Как изучение Хаскелла помогло мне понять flatMap на Java

Применение функциональных концепций в Java

Автор оригинала: Damián Rafael Lattenero.

Обо мне

Я студент университета, закончил программист и разработчик программного обеспечения Java

Почему я хотел изучить Хаскелл, помогая понять flatMap на Java

Я хотел применять функциональное программирование в своих повседневных проектах.

Как я подошел к изучению Хаскелла, помогая понять flatMap на Java

Я начал изучать Хаскелл в университете, затем с другом, наконец, самостоятельно и с помощью StackOverflow. Я знал, что эти концепции когда-нибудь пригодятся и могут быть применены с помощью простого калькулятора в Haskell:

data Expr = Val Int | Div Expr Expr

eval :: Expr -> Int
eval (Val n) = n
eval (Div x y) = div (eval x) (eval y)

n1 = Val 8
n2 = Val 4
n3 = Val 0 
d1 = Div n1 n2
d2 = Div d1 d1
d3 = Div d2 n3

main = do
  putStrLn $ show (eval d2)

Он напечатает, как вы можете видеть, “1”. Но этот пример, очевидно, приводит меня к случаю “нулевого деления”, и простой способ его эмулировать-использовать тип “Возможно”.:

data Expr = Val Int | Div Expr Expr

eval :: Expr -> Maybe Int
eval (Val n) = Just n
eval (Div x y) = do
                  v1 <- eval x
                  v2 <- eval y
                  if v2 == 0 
                  then Nothing
                  else return (div v1 v2)
n1 = Val 8
n2 = Val 4
n3 = Val 0 
d1 = Div n1 n2
d2 = Div d1 d1
d3 = Div d2 n3

main = do
  putStrLn $ show (eval d2)
  putStrLn $ show (eval d3)

Поехали! теперь мы получили результат:

Just 1
Nothing

Проблемы, с которыми я столкнулся

Теперь, как перевести этот пример в код Java?

Во-первых: Мы создаем наш метод в интерфейсе:

import java.util.Optional;
public interface Expr {

    public Optional eval();

}

Тогда нам нужны кейсы для Вэл и Экспра, давайте начнем с самого простого, Вэл:

import java.util.Optional;

public class Val implements Expr{

    Optional value;

    public Val(int value) {
        this.value = Optional.of(value);
    }

    @Override
    public Optional eval() {
        return value;
    }
}

Теперь возникает небольшой поворот, как создать рекурсивную часть в java с помощью опции? Давайте взглянем:

import java.util.Optional;

public class Div implements Expr {

    Expr expr1;
    Expr expr2;

    public Div(Expr expr1, Expr expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    @Override
    public Optional eval() {
        return expr1.eval().flatMap(v1 ->
                expr2.eval().flatMap(v2 ->
                    (v2 == 0) ? Optional.empty() : Optional.of(v1 / v2)
                )
               );
    }

    public static void main(String[] args) {
        Expr iv1 = new Val(6);
        Expr iv2 = new Val(3);
        Expr iv3 = new Val(2);
        Expr iv4 = new Val(0);
        Expr div1 = new Div(iv1, iv2);
        Expr div2 = new Div(div1, iv3);
        Expr div3 = new Div(div2, iv4);

        System.out.println(div2.eval());
        System.out.println(div3.eval());

    }
}

он будет печатать:

Optional[1]
Optional.empty

Здесь мы переводим нашу запись в две плоские карты, эквивалентные методу”>>=”, в Хаскелле рекурсивный тип может быть переписан с помощью:

eval (Div x y)  = eval x >>= (\v1 -> eval y >>= \v2 -> if v2 == 0 then Nothing else return (div v1 v2))

эти два наших flatMap() с Java.

Ключевые выносы

не так очевидно, когда и где использовать flatMap, или как объединить его с другой плоской картой или с картой. Ключ в том, чтобы понять, вернет ли функция вам другой уровень в монаде или нет. Еще один быстрый пример, чтобы убедиться в этом, приведен на этом простом примере:

public class MainTest {
    public static void main(String[] args) {

        Optional o1 = Optional.of(1);
        Optional o2 = Optional.of(0);

        Optional integer1 = o1.map(i -> i + 2);

        Optional>> integer = o1.map(i -> o2.map(i2 -> div(i, i2)));
        Optional> integer2 = o1.flatMap(i -> o2.map(i2 -> div(i, i2)));
        Optional integer3 = o1.flatMap(i -> o2.flatMap(i2 -> div(i, i2)));
        Optional integer4 = o1.flatMap(i -> o2.map(i2 -> sum(i, i2)));
        Optional> integer5 = o1.map(i -> o2.map(i2 -> sum(i, i2)));

        List l1 = Arrays.asList(5,4,6);
        List l2 = Arrays.asList(2,4,5);
        List l3 = new ArrayList<>();

        l1.forEach(e -> {
            l2.forEach(e2 -> {
                if(e > e2){
                    l3.add(e + e2);
                }
            });
        });

        List collect = l1.stream().flatMap(e ->
                l2.stream().flatMap(e2 -> e > e2 ? Stream.of(e + e2) : Stream.empty()))
                .collect(Collectors.toList());

        List collect2 = l1.stream().flatMap(e -> l2.stream().flatMap(e2 -> Stream.of(e + e2))).collect(Collectors.toList());
        List collect3 = l1.stream().flatMap(e -> l2.stream().map(e2 -> e + e2)).collect(Collectors.toList());

        System.out.println(collect);
        System.out.println(collect2);
        System.out.println(collect3);
        System.out.println(l3);
    }

    public static Optional div(Integer i, Integer i2){
        if(i2 == 0){
            return Optional.empty();
        }else{
            return Optional.of(Math.floorDiv(i, i2));
        }
    }

    public static Integer sum(Integer i, Integer i2){
        return i+i2;
    }
}

С помощью этого примера можно поиграть и посмотреть как подходит тип и как получаются результаты.

Советы и рекомендации

Функциональное программирование-наш друг, и оно может быть применено в нашей повседневной работе, если мы сможем понять, как это сделать. Это действительно фантастика.

Заключительные мысли и следующие шаги

Моя следующая цель-сделать еще один шаг в flatMap, но с потоками Java я приведу краткий пример. Но я хочу пойти глубже.

Оригинал: “https://www.codementor.io/@damianlattenero/how-i-learned-haskell-helping-to-understand-flatmap-in-java-xng8fwcu2”