Монади с Java 8

В интерес на разбирането какво е монада, може ли някой да даде пример с помощта на java? възможни ли са те

Ламбда изразите са възможни с помощта на java, ако изтеглите предварителната версия, съвместима с ламбда JDK8 от тук http://jdk8.java.net/lambda/

Пример за ламбда, използваща този JDK, е показан по-долу, може ли някой да предостави сравнително проста монада?

public interface TransformService {
        int[] transform(List<Integer> inputs);
    }
    public static void main(String ars[]) {
        TransformService transformService = (inputs) -> {
            int[] ints = new int[inputs.size()];
            int i = 0;
            for (Integer element : inputs) {
                ints[i] = element;
            }
            return ints;
        };

        List<Integer> inputs = new ArrayList<Integer>(5) {{
            add(10);
            add(10);
        }};
        int[] results = transformService.transform(inputs);
    }

person NimChimpsky    schedule 19.11.2012    source източник
comment
Току-що публикувах пробна монада за Java8. github.com/jasongoodwin/better-java-monads   -  person JasonG    schedule 10.02.2015
comment
Съответен поток, незадължителен и CompletableFuture gist.github.com/fmcarvalho/2be5ff6419f9d5d8df8c9c11fcd 45271   -  person Miguel Gamboa    schedule 10.05.2019
comment
имаш нужда от ++i някъде.   -  person Ray Tayek    schedule 13.09.2020
comment
Монадата е моделът на интерпретатора.   -  person Will Ness    schedule 29.10.2020


Отговори (9)


Само за информация:

Предложеният JDK8 Optional клас удовлетворява трите Закони за монадите. Ето същност, демонстрираща това.

Всичко, което е необходимо да бъдеш Монада, е да осигуриш две функции, които отговарят на три закона.

Двете функции:

  1. Поставете стойност в монадичен контекст

    • Haskell's Maybe: return / Just
    • Опция на Scala: Some
    • Функционална опция на Java: Option.some
    • JDK8 по избор: Optional.of
  2. Приложете функция в монадичен контекст

    • Haskell's Maybe: >>= (aka bind)
    • Опция на Scala: flatMap
    • Функционална опция на Java: flatMap
    • JDK8 по избор: flatMap

Моля, вижте същността по-горе за демонстрация в Java на трите закона.

ЗАБЕЛЕЖКА: Едно от ключовите неща, които трябва да разберете, е сигнатурата на функцията за прилагане в монадичен контекст: тя приема необработения тип стойност и връща монадичния тип.

С други думи, ако имате екземпляр на Optional<Integer>, функциите, които можете да предадете на неговия flatMap метод, ще имат сигнатурата (Integer) -> Optional<U>, където U е тип стойност, който не е задължително да бъде Integer, например String:

Optional<Integer> maybeInteger = Optional.of(1);

// Function that takes Integer and returns Optional<Integer>
Optional<Integer> maybePlusOne = maybeInteger.flatMap(n -> Optional.of(n + 1));

// Function that takes Integer and returns Optional<String>
Optional<String> maybeString = maybePlusOne.flatMap(n -> Optional.of(n.toString));

Нямате нужда от какъвто и да било интерфейс на Monad, за да кодирате по този начин или да мислите по този начин. В Scala не кодирате към интерфейс на Monad (освен ако не използвате библиотека Scalaz...). Изглежда, че JDK8 ще даде възможност на Java хората да използват и този стил на верижни монадични изчисления.

Надявам се това да е полезно!

Актуализация: В блог за това тук.

person ms-tg    schedule 12.11.2013
comment
Голямата разлика между Java и Scala е монадичната for конструкция. Монадите са много по-малко приятни без тази специална синтактична поддръжка. - person Marko Topolnik; 24.09.2016
comment
Optional на Java не е съвсем действителна монада, защото законите на монадите могат да бъдат нарушени чрез използване на произвеждане null: developer.atlassian.com/blog/2015/08/optional-broken - person Blaisorblade; 09.11.2016
comment
@Blaisorblade Това не се казва в препратката ви. Optional е монада с flatMap като оператор за свързване. - person lledr; 15.01.2017

Java 8 ще има ламбда; монадите са съвсем различна история. Те са достатъчно трудни за обяснение във функционалното програмиране (както се вижда от големия брой уроци по темата в Haskell и Scala).

Монадите са типична характеристика на статично типизираните функционални езици. За да ги опишете на OO-говор, можете да си представите Monad интерфейс. Класовете, които имплементират Monad тогава ще бъдат наречени „монадични“, при условие че при имплементирането на Monad имплементацията се подчинява на това, което е известно като „монадните закони“. След това езикът предоставя известна синтактична захар, която прави работата с екземпляри на класа Monad интересна.

Сега Iterable в Java няма нищо общо с монадите, но като пример за тип, който Java компилаторът третира специално (синтаксисът foreach, който дойде с Java 5), ​​помислете за това:

Iterable<Something> things = getThings(..);
for (Something s: things) {  /* do something with s */ }

Така че докато можехме да използваме Iterator методите на Iterable (hasNext и компания) в цикъл for в стар стил, Java ни предоставя тази синтактична захар като специален случай.

Така че точно както класовете, които прилагат Iterable и Iterator, трябва да се подчиняват на Iterator законите (Пример: hasNext трябва да върне false, ако няма следващ елемент), за да бъдат полезни в foreach синтаксиса - ще има няколко монадични класа, които биха да бъде полезно със съответна нотация do (както се нарича в Haskell) или нотация for на Scala.

So -

  1. Какви са добрите примери за монадични класове?
  2. Как би изглеждала синтактичната захар за справяне с тях?

В Java 8, не знам - запознат съм с ламбда нотацията, но не съм запознат с друга специална синтактична захар, така че ще трябва да ви дам пример на друг език.

Монадите често служат като класове контейнери (списъците са пример). Java вече има java.util.List, което очевидно не е монадично, но ето това на Scala:

val nums = List(1, 2, 3, 4)
val strs = List("hello", "hola")
val result = for { // Iterate both lists, return a resulting list that contains 
                   // pairs of (Int, String) s.t the string size is same as the num.
  n <- nums        
  s <- strs if n == s.length 
} yield (n, s)
// result will be List((4, "hola")) 
// A list of exactly one element, the pair (4, "hola")

Което е (приблизително) синтактична захар за:

val nums = List(1, 2, 3, 4)
val strs = List("hello", "hola")
val results = 
nums.flatMap( n =>                 
  strs.filter(s => s.size == n).   // same as the 'if'
       map(s => (n, s))            // Same as the 'yield'
)
// flatMap takes a lambda as an argument, as do filter and map
// 

Това показва функция на Scala, при която монадите се използват за предоставяне на разбиране на списъци.

Така че List в Scala е монада, защото се подчинява на законите на монадите на Scala, които постановяват, че всички реализации на монади трябва да имат съответстващи flatMap, map и filter методи (ако се интересувате от законите, записът в блога „Монадите са слонове“ има най-доброто описание, което съм намирал досега). И, както можете да видите, ламбда (и HoF) са абсолютно необходими, но не достатъчни, за да направят този вид неща полезни по практически начин.

Има куп полезни монади освен контейнерните. Имат всякакви приложения. Любимата ми трябва да е монадата Option в Scala (монадата Maybe в Haskell), която е тип обвивка, която осигурява нулева безопасност: страницата на Scala API за монадата Option има много проста примерна употреба: http://www.scala-lang.org/api/current/scala/Option.html В Haskell монадите са полезни при представянето на IO, като начин за заобикаляне на факта, че немонадичният код на Haskell има неопределен ред на изпълнение.

Наличието на ламбда е първата малка стъпка в света на функционалното програмиране; монадите изискват както конвенцията за монадите, така и достатъчно голям набор от използваеми монадични типове, както и синтактична захар, за да бъде работата с тях забавна и полезна.

Тъй като Scala е може би най-близкият до Java език, който също позволява (монадично) функционално програмиране, вижте този урок за Monad за Scala, ако (все още) се интересувате: http://james-iry.blogspot.jp/2007/09/monads-are-elephants-part-1.html

Бегло гугъл показва, че има поне един опит да се направи това в Java: https://github.com/RichardWarburton/Monads-in-Java -

За съжаление, обяснението на монадите в Java (дори с ламбда) е толкова трудно, колкото обяснението на пълноценно обектно-ориентирано програмиране в ANSI C (вместо в C++ или Java).

person Faiz    schedule 19.11.2012
comment
yowzers, благодаря. Все още съм малко на тъмно, ще разширя четенето си и може би дори ще направя крачката и ще опитам малко скала. - person NimChimpsky; 19.11.2012
comment
Ура! (да чуя, че може да опитате Scala) - Ще откриете, че можете да свършите много неща, да бъдете супер продуктивни, всичко това без активно да учите монади в Scala - и тогава един ден неочаквано изведнъж ще откриете, че вече познавате монадите доста добре! - person Faiz; 19.11.2012
comment
Опционалната монада в Java без ламбда: functionaljava.org/examples/1.5/#Option.bind. Разгледайте и можете да намерите много FP в java. - person pedrofurla; 15.03.2013
comment
@Faiz, така че какво точно означава терминът монада?? друг вид интерфейс ли е, който трябва да прилага абстрактни методи?? Накратко, ако някой попита какво е монада, какъв би бил най-краткият отговор? - person nish1013; 27.09.2013
comment
@nish1013 Интерфейсът е само възможна реализация в Java, на теория Monad е абстракция на изчислителен процес в някакъв контекст. Споменатата по-горе опция Monad е контекст за абстрахиране на някои възможни изчисления, т.е. можете да имате няколко функции, които могат да връщат или някаква стойност, или нито една, и можете да изградите изчисление, където всяка стъпка зависи от предишната изчислена стойност, или има Бъдеща монада, която описва контекст на някои отложени изчисления, IO монада, държавна монада и т.н. =) - person 4lex1v; 08.10.2013
comment
Ако твърдението ви в горната част е, че монадите са предназначени само за статично въведени езици, бих поставил под въпрос това. Всеки език с първокласни функции е напълно подходящ. Голяма част от Javascript там е монадичен. - person Marco Faustinelli; 28.04.2014
comment
Не съм съгласен с първото ти твърдение. Не мисля, че големият брой уроци за монади за Haskell и Scala показва, че са трудни за обяснение. Вместо това мисля, че те съществуват, за да обяснят голямото разнообразие от мощни монади, които носят. Трудното при монадите е, че въпреки че са много интуитивни, но е трудно да се дефинират. Следователно обяснението чрез пример е най-ефективно. - person DCKing; 30.04.2014
comment
За хората, които търсят примери за Java по-долу, има хубав набор от примери, които използват API на потока. stackoverflow.com/a/34668089/887836 - person Alexander Oh; 24.11.2016

Въпреки че монадите могат да бъдат внедрени в Java, всяко изчисление, което ги включва, е обречено да се превърне в объркана комбинация от генерични и фигурни скоби.

Бих казал, че Java определено не е езикът, който да се използва, за да се илюстрира тяхната работа или да се проучи тяхното значение и същност. За тази цел е много по-добре да използвате JavaScript или да платите допълнителна цена и да научите Haskell.

Както и да е, сигнализирам ви, че току-що имплементирах държавна монада с помощта на новите Java 8 lambdas. Това определено е домашен любимец, но работи върху нетривиален тестов случай.

Може да го намерите представен в моя блог, но ще ви дам някои подробности тук.

Монадата на състоянието е основно функция от състояние към двойка (състояние, съдържание). Обикновено давате на състоянието генеричен тип S, а на съдържанието генеричен тип A.

Тъй като Java няма двойки, трябва да ги моделираме с помощта на специфичен клас, нека го наречем Scp (двойка състояние-съдържание), който в този случай ще има общ тип Scp<S,A> и конструктор new Scp<S,A>(S state,A content). След като направим това, можем да кажем, че монадичната функция ще има тип

java.util.function.Function<S,Scp<S,A>>

което е @FunctionalInterface. Това означава, че неговият единствен метод за внедряване може да бъде извикан, без да го наименува, предавайки ламбда израз с правилния тип.

Класът StateMonad<S,A> е основно обвивка около функцията. Неговият конструктор може да бъде извикан напр. с

new StateMonad<Integer, String>(n -> new Scp<Integer, String>(n + 1, "value"));

Монадата на състоянието съхранява функцията като променлива на екземпляр. След това е необходимо да се осигури публичен метод за достъп до него и захранване на държавата. Реших да го нарека s2scp ("двойка състояние към състояние-съдържание").

За да завършите дефиницията на монадата, трябва да предоставите unit (известен още като return) и bind (известен още като flatMap >) метод. Лично аз предпочитам да посоча единица като статична, докато bind е член на екземпляр.

В случая на държавната монада единицата трябва да бъде следната:

public static <S, A> StateMonad<S, A> unit(A a) {
    return new StateMonad<S, A>((S s) -> new Scp<S, A>(s, a));
}

докато bind (като член на екземпляр) е:

public <B> StateMonad<S, B> bind(final Function<A, StateMonad<S, B>> famb) {
    return new StateMonad<S, B>((S s) -> {
        Scp<S, A> currentPair = this.s2scp(s);
        return famb(currentPair.content).s2scp(currentPair.state);
    });
}

Забелязвате, че bind трябва да въведе генеричен тип B, защото това е механизмът, който позволява верижното свързване на монади с хетерогенно състояние и дава на тази и всяка друга монада забележителната способност да преместват изчислението от тип на тип.

Бих спрял тук с Java кода. Сложните неща са в проекта GitHub. В сравнение с предишните версии на Java, ламбда премахват много фигурни скоби, но синтаксисът все още е доста сложен.

Като настрана, показвам как подобен код на монада на състояние може да бъде написан на други масови езици. В случая на Scala свързването (което в този случай трябва да се нарича flatMap) се чете като

def flatMap[A, B](famb: A => State[S, B]) = new State[S, B]((s: S) => {
  val (ss: S, aa: A) = this.s2scp(s)
  famb(aa).s2scp(ss)
})

като има предвид, че свързването в JavaScript е любимото ми; 100% функционален, прост и подъл, но - разбира се - без тип:

var bind = function(famb){
    return state(function(s) {
        var a = this(s);
        return famb(a.value)(a.state);
    });
};

‹shameless› Изрязвам няколко ъгъла тук, но ако се интересувате от подробности, ще ги намерите в моя WP блог.‹/shameless›

person Marco Faustinelli    schedule 27.04.2014
comment
Не поставям под въпрос, че имате сериозен и полезен отговор, но важната част от него (как всъщност го внедрихте в Java) е скрита зад връзка. Връзките остаряват с течение на времето и поради тази причина политиката на SO е да включите важните аспекти на вашия отговор в самия отговор, а не във връзка. Тъй като връзката е към вашата собствена страница, сигурен съм, че можете да обобщите най-важната част от нея и да я поставите в отговора си тук. - person Erwin Bolwidt; 27.04.2014
comment
Добра точка. съжалявам Ще го направя преди края на деня. Междувременно, моля, не гласувайте против мен :-):-):-):-) - person Marco Faustinelli; 27.04.2014

Ето нещото за монадите, което е трудно за разбиране: монадите са модел, а не конкретен тип. Монадите са форма, те са абстрактен интерфейс (не в смисъла на Java), отколкото конкретна структура от данни. В резултат на това всеки ръководен от примери урок е обречен на непълнота и провал. [...] Единственият начин да разберем монадите е да ги видим такива, каквито са: математическа конструкция.

Монадите не са метафори от Daniel Spiewak


Монади в Java SE 8

Списък монада

interface Person {
    List<Person> parents();

    default List<Person> greatGrandParents1() {
        List<Person> list = new ArrayList<>();
        for (Person p : parents()) {
            for (Person gp : p.parents()) {
                for (Person ggp : p.parents()) {

                    list.add(ggp);
                }
            }
        }
        return list;
    }

    // <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
    default List<Person> greatGrandParents2() {
        return Stream.of(parents())
                .flatMap(p -> Stream.of(p.parents()))
                .flatMap(gp -> Stream.of(gp.parents()))
                .collect(toList());
    }
}

Може би монада

interface Person {
    String firstName();
    String middleName();
    String lastName();

    default String fullName1() {
        String fName = firstName();
        if (fName != null) {
            String mName = middleName();
            if (mName != null) {
                String lName = lastName();
                if (lName != null) {
                    return fName + " " + mName + " " + lName;
                }
            }
        }
        return null;
    }

    // <U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
    default Optional<String> fullName2() {
        return Optional.ofNullable(firstName())
                .flatMap(fName -> Optional.ofNullable(middleName())
                .flatMap(mName -> Optional.ofNullable(lastName())
                .flatMap(lName -> Optional.of(fName + " " + mName + " " + lName))));
    }
}

Monad е генеричен шаблон за вложен капсулиране на контролен поток. т.е. начин за създаване на многократно използвани компоненти от вложени императивни идиоми.

Важно е да разберете, че монадата не е просто общ клас обвивка с операция плоска карта. Например ArrayList с метод flatMap няма да бъде монада. Тъй като монадните закони забраняват страничните ефекти.

Монадата е формализъм. Той описва структурата, независимо от съдържанието или смисъла. Хората се борят да се свързват с безсмислени (абстрактни) неща. Така че те измислят метафори, които не са монади.

Вижте също: разговор между Erik Meijer и Gilad Bracha.

person user2418306    schedule 08.01.2016
comment
Имайте предвид, че в Java fName + " " + middleName() никога не може да върне null, така че Optional.ofNullable е подвеждащо. Вероятно сте искали Optional.ofNullable(middleName()).map(mName -> fName + " " + mName)? - person Tagir Valeev; 26.01.2016

единственият начин да разберете монадите е като напишете куп комбинаторни библиотеки, забележите произтичащото дублиране и след това сами откриете, че монадите ви позволяват да факторизирате това дублиране. Откривайки това, всеки изгражда някаква интуиция за това какво е монада... но тази интуиция не е нещото, което можете да съобщите директно на някой друг – изглежда, че всеки трябва да премине през същото преживяване на обобщаване на монади от някакво конкретно примери за библиотеки на комбинатори. въпреки това

тук намерих някои материали, за да науча Mondas.

надявам се да съм полезна и на вас.

codecommit

james-iry.blogspot

debasishg.blogspot

person Morteza Adi    schedule 19.11.2012
comment
всички те се свързват със скала? Търсех изпълнение на Java - person NimChimpsky; 19.11.2012
comment
Опитах се да обясня защо внедряването на Java би било трудно. - person Faiz; 19.11.2012

Тази публикация в блог дава пример стъпка по стъпка за това как можете да имплементирате тип Monad (интерфейс) в Java и след това да го използвате, за да дефинирате монадата Maybe като практическо приложение.

Тази публикация обяснява, че има една монада, вградена в езика Java, като набляга на отбелязват, че монадите са по-често срещани, отколкото много програмисти могат да мислят, и че програмистите често неволно да ги преоткриете.

person Katie J. Ots    schedule 18.12.2012
comment
правилна връзка: blog.tmorris.net/posts/maybe-monad-in- java - person Ray Tayek; 13.09.2020

Въпреки всички противоречия относно това дали Optional удовлетворява или не законите на Монадата, обикновено обичам да гледам на Stream, Optional и CompletableFuture по същия начин. Всъщност всички те осигуряват flatMap() и това е всичко, което ме интересува и ми позволи да прегърна „вкусния състав на страничните ефекти“ (цитиран от Erik Meijer). Така че можем да имаме съответстващи Stream, Optional и CompletableFuture по следния начин:

Що се отнася до монадите, обикновено ги опростявам, като мисля само за flatMap()(от курса „Принципи на реактивното програмиране“ от Erik Meijer):

Eric-Meijer-flatMap

person Miguel Gamboa    schedule 10.05.2019

Харесва ми да мисля за монадите в малко по-математически (но все пак неофициален) начин. След това ще обясня връзката с една от монадите на Java 8 CompletableFuture.

На първо място, монадата M е функтор. Тоест трансформира тип в друг тип: Ако X е тип (напр. String), тогава имаме друг тип M<X> (напр. List<String>). Освен това, ако имаме трансформация/функция X -> Y от типове, трябва да получим функция M<X> -> M<Y>.

Но има повече данни за такава монада. Имаме така наречената единица, която е функция X -> M<X> за всеки тип X. С други думи, всеки обект от X може да бъде обвит по естествен начин в монадата.

Най-характерните данни за една монада обаче са нейният продукт: функция M<M<X>> -> M<X> за всеки тип X.

Всички тези данни трябва да отговарят на някои аксиоми като функционалност, асоциативност, закони на единиците, но няма да навлизам в подробности тук и това също няма значение за практическа употреба.

Сега можем да изведем друга операция за монади, която често се използва като еквивалентна дефиниция за монади, операцията за свързване: Стойност/обект в M<X> може да бъде обвързана с функция X -> M<Y>, за да се получи друга стойност в M<Y>. Как да постигнем това? Е, първо прилагаме функционалност към функцията, за да получим функция M<X> -> M<M<Y>>. След това прилагаме монадичния продукт към целта, за да получим функция M<X> -> M<Y>. Сега можем да включим стойността на M<X>, за да получим стойност в M<Y> по желание. Тази операция на свързване се използва за свързване на няколко монадични операции заедно.

Сега да преминем към примера CompletableFuture, т.е. CompletableFuture = M. Мислете за обект от CompletableFuture<MyData> като за някакво изчисление, което се извършва асинхронно и което дава обект от MyData като резултат известно време в бъдещето. Какви са монадичните операции тук?

  • функционалността се реализира с метода thenApply: първо се извършва изчислението и веднага щом резултатът е наличен, функцията, дадена на thenApply, се прилага за трансформиране на резултата в друг тип
  • монадичната единица се реализира с метода completedFuture: както се казва в документацията, полученото изчисление вече е завършено и дава дадената стойност наведнъж
  • монадичният продукт не се реализира от функция, но свързващата операция по-долу е еквивалентна на нея (заедно с функционалността) и нейното семантично значение е просто следното: дадено изчисление от тип CompletableFuture<CompletableFuture<MyData>> това изчисление асинхронно дава друго изчисление в CompletableFuture<MyData>, което на свой ред дава някаква стойност в MyData по-късно, така че извършването на двете изчисления след другото дава общо едно изчисление
  • получената операция на свързване се реализира по метода thenCompose

Както виждате, изчисленията вече могат да бъдат обвити в специален контекст, а именно асинхронност. Общите монадични структури ни позволяват да свържем такива изчисления в дадения контекст. CompletableFuture например се използва в рамката Lagom за лесно конструиране на силно асинхронни манипулатори на заявки, които са прозрачно архивирани от ефективни пулове от нишки (вместо всяка заявка да се обработва от специална нишка).

person Werner Thumann    schedule 07.07.2017

Диаграма за незадължителната монада в Java.

Вашата задача: Извършете операции върху Actuals (лявата страна), трансформиращи елементи от тип T обединение null в тип U обединение null, като използвате функцията в светлосинята кутия (функция светлосиня кутия). Тук е показана само една кутия, но може да има верига от светлосини кутии (по този начин се преминава от тип U обединение null към тип V _обединение null до тип W обединение null и т.н.)

На практика това ще ви накара да се тревожите за null стойности, които се появяват във веригата на приложението на функцията. Грозно!

Решение: Опаковайте своя T в Optional<T>, като използвате функциите на светлозелената кутия, като преминете към опциите (дясната страна). Тук преобразувайте елементи от тип Optional<T> в тип Optional<U> с помощта на функцията червена кутия. Отразявайки приложението на функции към действителните стойности, може да има няколко функции на червена кутия, които да бъдат свързани във верига (като по този начин се преминава от тип Optional<U> към Optional<V>, след това към Optional<W> и т.н.). Накрая се върнете от опцията към действителността чрез една от функциите на тъмнозелената кутия.

Вече няма да се притеснявате за стойностите на null. По отношение на изпълнението винаги ще има Optional<U>, което може или не може да е празно. Можете да свържете обажданията към функциите на червената кутия без нулеви проверки.

Ключовият момент: Функциите на червената кутия не се изпълняват индивидуално и директно. Вместо това те се получават от функциите на синята кутия (които от тях са внедрени и налични, обикновено светлосините) чрез използване на map или flatMap функциите от по-висок ред.

Сивите полета осигуряват допълнителна функционалност за поддръжка.

Простички.

Java по избор, как да го използвам

person David Tonhofer    schedule 29.10.2020