Автор оригинала: 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 Optionaleval(); }
Тогда нам нужны кейсы для Вэл и Экспра, давайте начнем с самого простого, Вэл:
import java.util.Optional; public class Val implements Expr{ Optionalvalue; 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 Optionaleval() { 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) { Optionalo1 = 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”