Монади з Java 8


78

Щоб допомогти зрозуміти, що таке монада, чи може хтось навести приклад використання 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);
    }

1
Я щойно опублікував спробу монади для Java8. github.com/jasongoodwin/better-java-monads
JasonG


вам десь потрібен ++ i.
Ray Tayek

Монада - це модель перекладача.
Will Ness

Відповіді:


78

Просто FYI:

Запропонований необов'язковий клас JDK8 відповідає трьом законам Монади . Ось суть, що це демонструє .

Для того, щоб бути Монадою, потрібно лише забезпечити дві функції, які відповідають трьом законам.

Дві функції:

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

    • Можливо, Хаскелл: return/Just
    • Варіант Scala: Some
    • Функціональний варіант Java: Option.some
    • Необов’язково JDK8: Optional.of
  2. Застосувати функцію в монадичному контексті

    • Haskell's Maybe: >>=(він же 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 також використовувати цей стиль ланцюгових монадичних обчислень .

Сподіваюся, це корисно!

Оновлення: Блог про це тут .


Великим розривом між Java та Scala є монадична forконструкція. Монади набагато менш приємні без спеціальної синтаксичної підтримки.
Marko Topolnik

3
Java Optionalне є справжньою монадою, оскільки закони монади можуть бути порушені за допомогою продукування null: developer.atlassian.com/blog/2015/08/optional-broken
Blaisorblade

1
@Blaisorblade Це не те, про що говорить ваше посилання. Optionalє монадою, з flatMapоператором прив'язки.
lledr

56

Java 8 матиме лямбди; монади - це зовсім інша історія. Їх досить важко пояснити у функціональному програмуванні (про що свідчить велика кількість навчальних посібників з цього питання в Хаскеллі та Скалі).

Монади - типова риса статично набраних функціональних мов. Щоб описати їх у OO-говорі, ви можете уявити Monadінтерфейс. Класи, які реалізовуються Monad, тоді називатимуться «монадичними», за умови, що при реалізації Monadімплементація дотримується так званих «законів монади». Потім мова надає деякий синтаксичний цукор, що робить роботу з екземплярами Monadкласу цікавою.

Зараз Iterableу Java немає нічого спільного з монадами, але як приклад типу, до якого компілятор Java звертається спеціально ( foreachсинтаксис, що поставляється з Java 5), ​​розглянемо це:

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

Таким чином , в той час як ми могли б використовувати Iterable«s Iteratorметоди ( hasNextі компанії) в старому стилі forциклу, Java надає нам цей синтаксичний цукор як окремий випадок .

Отже, як класи, які реалізують Iterableі Iteratorповинні підкорятися Iteratorзаконам (Приклад: hasNextповинні повертатись, falseякщо немає наступного елемента), щоб бути корисними в foreach синтаксисі - існувало б кілька монадичних класів, які були б корисними з відповідними doпозначеннями (як це називається в Haskell ) або forпозначення Скали .

Так -

  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методи (якщо ви зацікавлені в законах, то «Монада Слони» запис в блозі має кращий опис I » я знайшов на сьогодні). І, як бачите, лямбди (і HoF) абсолютно необхідні, але недостатні, щоб зробити подібні речі корисними на практиці.

Існує купа корисних монад, крім контейнерних. Вони мають усі види додатків. Моїм улюбленим повинна бути Optionмонада в Scala ( Maybeмонада в Haskell), тобто тип обгортки, який забезпечує нульову безпеку : сторінка API Scala для 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/ монади-це-слони-частина-1.html

Побіжне гуглиння показує, що є хоча б одна спроба зробити це на Java: https://github.com/RichardWarburton/Monads-in-Java -

На жаль, пояснити монади на Java (навіть з лямбдами) так само важко, як пояснити повномасштабне об'єктно-орієнтоване програмування на ANSI C (замість C ++ або Java).


йоузери, дякую. Я все ще трохи в темряві, буду ширше читати, і, можливо, навіть поважуюся і пробую якусь шкалу.
NimChimpsky

4
Ага! (щоб почути, що ви можете спробувати Scala) - Ви побачите, що можете робити безліч речей, бути суперпродуктивними, і все це без активного вивчення монад у Scala - і тоді одного дня несподівано ви раптом виявите, що вже досить добре знаєте монади!
Фаїз,

1
Опція монади в Java без лямбда: functionaljava.org/examples/1.5/#Option.bind . Переглядайте, і ви можете знайти багато FP в Java.
pedrofurla

@Faiz так що саме означає термін "монада" ?? це інший вид інтерфейсу, який повинен реалізовувати абстрактні методи ?? Коротше кажучи, якби хтось запитав, що таке "монада", що було б найкоротшою відповіддю?
nish1013

2
Я не згоден з вашим першим твердженням. Я не думаю, що велика кількість підручників з монад для Haskell та Scala вказує на те, що їх важко пояснити. Натомість я думаю, що вони існують, щоб пояснити велике різноманіття влади, яку приносять монади. Складність монад полягає в тому, що вони хоч і дуже інтуїтивні, але важко визначити. Тому пояснення на прикладі є найбільш ефективним.
DCKing

11

Незважаючи на те, що монади можуть бути реалізовані на Java, будь-які обчислення, що їх залучають, приречені стати безладним поєднанням дженериків та фігурних дужок.

Я б сказав, що Java, безумовно, не є тією мовою, яка використовується для того, щоб проілюструвати їх роботу чи вивчити їх значення та суть. Для цього набагато краще використовувати JavaScript або заплатити якусь додаткову ціну та вивчити Haskell.

У будь-якому випадку, я повідомляю вам, що я щойно реалізував монаду стану, використовуючи нові лямбди Java 8 . Це, безумовно, проект для домашніх тварин, але він працює на нетривіальному тестовому випадку.

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

Державна монада в основному є функцією від стану до пари (стану, вмісту) . Зазвичай ви надаєте штату загальний тип 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("пара до стану-вмісту").

Щоб завершити визначення монади, вам потрібно надати одиницю (вона ж повернена ) та метод прив'язки (він же 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));
}

while 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, bind (який у цьому випадку повинен називатися 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>


1
Я не сумніваюся, що у вас є серйозна та корисна відповідь, але важлива її частина (як ви насправді реалізували її на Java) ховається за посиланням. Посилання з часом застарівають, і з цієї причини саме політика SO повинна включати важливі аспекти вашої відповіді в саму відповідь, а не в посилання. Оскільки це ваша власна сторінка, на яку ви посилалися, я впевнений, ви можете резюмувати найважливішу її частину та помістити її у своїй відповіді тут.
Ервін Болвідт,

Влучне зауваження. Вибачте. Я зроблю це до кінця дня. Тим часом, будь ласка, не голосуйте проти мене :-) :-) :-) :-)
Марко Фаустінеллі

5

єдиний спосіб зрозуміти монади - це написати купу комбінаторних бібліотек, помітити результуюче дублювання, а потім виявити для себе, що монади дозволяють вам врахувати це дублювання. Виявляючи це, кожен вибудовує певну інтуїцію щодо того, що таке монада ... але ця інтуїція не є тим, що ви можете спілкуватися з кимось іншим - здається, кожен повинен пройти той самий досвід узагальнення для монад з певного приклади бібліотек-комбінаторів. однак

тут я знайшов кілька матеріалів для вивчення Мондаса.

сподіваюся бути корисним і вам.

codecommit

james-iry.blogspot

debasishg.blogspot


1
всі вони посилаються на скалу? Шукав реалізацію Java
NimChimpsky

Я намагався пояснити, чому реалізація Java буде важкою.
Faiz

5

Ось така річ про монади, яку важко зрозуміти: монади - це шаблон, а не конкретний тип. Монади - це форма, це абстрактний інтерфейс (не в сенсі Java), більше, ніж конкретна структура даних. Як результат, будь-який підручник, керований прикладами, приречений на неповноту та невдачу. [...] Єдиний спосіб зрозуміти монади - це побачити їх такими, якими вони є: математична конструкція.

Монади - це не метафори Даніеля Співака


Монади в 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методу не буде монадою. Оскільки закони про монаду забороняють побічні ефекти.

Монада - це формалізм . Він описує структуру, незалежно від змісту чи значення. Люди борються із стосунками до безглуздих (абстрактних) речей. Тож вони придумують метафори, які не є монадами.

Дивіться також: розмова між Еріком Мейєром та Гіладом Брачою.


Зверніть увагу, що в Java fName + " " + middleName()ніколи не може повернутися null, тому Optional.ofNullableце оманливе. Напевно, ти хотів Optional.ofNullable(middleName()).map(mName -> fName + " " + mName)?
Тагір Валєєв

2

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

У цій публікації пояснюється, що в мові Java вбудована одна монада, наголошуючи на тому, що монади є більш поширеними, ніж думають багато програмістів, і що кодери часто ненавмисно їх винаходять .


правильне посилання: blog.tmorris.net/posts/maybe-monad-in-java
Ray Tayek

1

Незважаючи на всі суперечки щодо Optionalзадоволення чи ні законів Монади, я, як правило, люблю дивитись Stream, Optionalі CompletableFutureтак само. По правді кажучи, всі вони забезпечують, flatMap()і це все, що я дбаю, і дозвольте мені прийняти " смаковий склад побічних ефектів " (цитував Ерік Мейер). Тож ми можемо мати відповідні Stream, Optionalі CompletableFutureтаким чином:

Щодо Монад, я, як правило, спрощую її, лише замислюючись flatMap()(з курсу " Принципи реактивного програмування " Еріка Мейєра):

Ерік-Мейєр-квартираКарта


0

Мені подобається думати про монади трохи більш математично (але все ще неформально). Після цього я розтлумачу стосунки з однією з монад 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 для легкої побудови дуже асинхронних обробників запитів, які прозоро резервуються за допомогою ефективних пулів потоків (замість обробки кожного запиту спеціальним потоком).


0

Схема "Необов’язкової" монади в Java.

Ваше завдання: Виконати операції на «» Фактичний (ліву сторону) , що перетворюють елементи типу T об'єднання null до типу U об'єднання null , використовуючи функцію в світло - блакитному блоці ( світло функція синьої коробки ). Тут показано лише одне поле, але може бути ланцюжок із світло-блакитних коробок (таким чином переходячи від U об'єднання null типу до типу V_union nullдо W об'єднання типу nullтощо)

Практично це призведе до занепокоєння щодо nullзначень, що з’являються в ланцюжку додатків функцій. Некрасиво!

Рішення: Загорніть Tу світ, Optional<T>використовуючи функції світло-зеленого вікна , переходячи до розділу "Додатково" (справа). Тут трансформуйте елементи типу Optional<T>в тип Optional<U>за допомогою функції червоного поля . Дзеркальне застосування функцій до «» фактично, там може бути кілька функцій червоною коробки , щоб бути прикуті (таким чином , виходячи з типу , Optional<U>щоб Optional<V>потім і Optional<W>т.д.). Зрештою, поверніться від "Необхідних" до "Фактичних" за допомогою однієї з темно-зелених функцій вікна .

Більше не турбуватися про nullцінності. Застосовуючи, завжди буде присутній Optional<U>, який може бути або не бути порожнім. Ви можете прив’язати виклики до функцій червоного ящика без нульових перевірок.

Ключовий момент: функції червоного ящика не реалізуються окремо та безпосередньо. Натомість вони отримуються із функцій синього ящика (залежно від того, яка з них була реалізована та доступна, як правило, світло-блакитна) за допомогою функцій вищого mapчи flatMapнижчого рівня.

Сірі рамки надають додаткові функції підтримки.

Прості.

Java Необов’язково, як ним користуватися

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.