Монады с 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
Я только что опубликовал монаду Try для Java8. github.com/jasongoodwin/better-java-monads   -  person JasonG    schedule 10.02.2015
comment
Соответствующий поток, необязательный и CompletableFuture   -  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));

Вам не нужен какой-либо монадный интерфейс, чтобы кодировать таким образом или думать таким образом. В Scala вы не кодируете интерфейс Monad (если только вы не используете библиотеку Scalaz...). Судя по всему, JDK8 также позволит любителям Java использовать этот стиль связанных монадических вычислений.

Надеюсь, это полезно!

Обновление: запись в блоге об этом здесь.

person ms-tg    schedule 12.11.2013
comment
Большой разрыв между Java и Scala — это монадическая конструкция for. Монады гораздо менее приятны без этой специальной синтаксической поддержки. - person Marko Topolnik; 24.09.2016
comment
Java-монада Optional не совсем настоящая монада, потому что законы монады могут быть нарушены с помощью создания null: noreferrer">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).

Монады являются типичной особенностью статически типизированных функциональных языков. Чтобы описать их на языке объектно-ориентированного программирования, вы можете представить себе 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) или нотацией Scala for.

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 (если вас интересуют законы, запись в блоге "Monads are Elephants" имеет лучшее описание, которое я нашел до сих пор). И, как видите, лямбда-выражения (и HoF) абсолютно необходимы, но не достаточны, чтобы сделать такие вещи полезными на практике.

Помимо контейнерных монад есть куча полезных монад. У них есть все виды приложений. Моей любимой должна быть монада Option в Scala (монада Maybe в Haskell), которая представляет собой тип оболочки, обеспечивающий нулевую безопасность: на странице API Scala для монады Option есть очень простой пример использования: http://www.scala-lang.org/api/current/scala/Option.html В Haskell монады полезны для представления операций ввода-вывода, как способ обойти тот факт, что немонадный код 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
юзерс, спасибо. Я все еще немного в темноте, буду расширять свое чтение и, возможно, даже сделаю решающий шаг и попробую немного scala. - person NimChimpsky; 19.11.2012
comment
Ура! (услышать, что вы могли бы попробовать Scala) – вы обнаружите, что можете делать массу вещей, быть сверхпродуктивным, и все это без активного изучения монад в Scala – и затем однажды неожиданно вы вдруг обнаружите, что уже достаточно хорошо знаете монады! - person Faiz; 19.11.2012
comment
@Faiz, так что именно означает термин монады ?? это другой вид интерфейса, который должен реализовывать абстрактные методы?? Короче говоря, если бы кто-то спросил, что такое монада, каким был бы самый короткий ответ? - person nish1013; 27.09.2013
comment
@nish1013 Интерфейс - это просто возможная реализация на Java, теоретически монада - это абстракция вычислительного процесса в некотором контексте. Упомянутая выше Option Monad является контекстом для абстрагирования от некоторых возможных вычислений, т.е. у вас может быть пара функций, которые могут возвращать либо Some value, либо None, и вы можете построить вычисление, где каждый шаг зависит от ранее вычисленного значения, или есть Будущая монада, описывающая контекст некоторых отложенных вычислений, монада IO, монада State и т.д. =) - person 4lex1v; 08.10.2013
comment
Если ваше утверждение вверху состоит в том, что монады предназначены только для статически типизированных языков, я бы поставил это под сомнение. Любой язык с первоклассными функциями идеально подходит. Многие Javascript монадичны. - person Marco Faustinelli; 28.04.2014
comment
Я не согласен с вашим первым утверждением. Я не думаю, что большое количество руководств по монадам для Haskell и Scala указывает на то, что их трудно объяснить. Вместо этого я думаю, что они существуют для того, чтобы объяснить огромное разнообразие сил, которые приносят монады. Сложность с монадами заключается в том, что, хотя они очень интуитивны, их трудно определить. Поэтому объяснение на примере наиболее эффективно. - person DCKing; 30.04.2014

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

Я бы сказал, что Java определенно не является тем языком, который можно использовать для иллюстрации их работы или изучения их значения и сути. Для этой цели гораздо лучше использовать JavaScript или доплатить и изучить Haskell.

В любом случае, я сообщаю вам, что я только что реализовал монаду состояния, используя новые лямбда-выражения Java 8. Это определенно любимый проект, но он работает на нетривиальном тестовом примере.

Вы можете найти его на моем blog, но здесь я дам вам некоторые подробности.

Монада состояния — это, по сути, функция перехода от состояния к паре (состояние, содержимое). Обычно вы присваиваете состоянию общий тип 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 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›

person Marco Faustinelli    schedule 27.04.2014
comment
Я не сомневаюсь, что у вас есть серьезный и полезный ответ, но важная его часть (как вы на самом деле реализовали его на Java) скрыта за ссылкой. Ссылки со временем устаревают, и по этой причине политика SO заключается в том, что вы должны включать важные аспекты своего ответа в сам ответ, а не в ссылку. Поскольку это ваша собственная страница, на которую вы ссылались, я уверен, что вы можете обобщить наиболее важную ее часть и поместить ее в свой ответ здесь. - person Erwin Bolwidt; 27.04.2014
comment
Хорошая точка зрения. Прости. Я сделаю это до конца дня. А пока, пожалуйста, не минусуйте меня :-):-):-):-) - person Marco Faustinelli; 27.04.2014

Вот что трудно понять о монадах: монады - это шаблон, а не конкретный тип. Монады — это форма, они представляют собой абстрактный интерфейс (не в смысле 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))));
    }
}

Монада — это общий шаблон для вложенной инкапсуляции потока управления. т.е. способ создания повторно используемых компонентов из вложенных императивных идиом.

Важно понимать, что монада — это не просто универсальный класс-оболочка с операцией flat map. Например, ArrayList с методом flatMap не будет монадой. Потому что законы монад запрещают побочные эффекты.

Монада — это формализм. Он описывает структуру, независимо от содержания или значения. Люди борются с отношением к бессмысленным (абстрактным) вещам. Поэтому они придумывают метафоры, которые не являются монадами.

См. также диалог между Эриком Мейером и Гиладом Брачей.

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.

надеюсь быть полезным и для вас.

кодкоммит

james-iry.blogspot

debasishg.blogspot

person Morteza Adi    schedule 19.11.2012
comment
все они ссылаются на scala? Искал внедрение 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(), и это все, что меня волнует, и позвольте мне принять «вкусный состав побочных эффектов» (цитируется Эриком Мейером). Таким образом, у нас могут быть соответствующие Stream, Optional и CompletableFuture следующим образом:

Что касается монад, я обычно упрощаю их, думая только о flatMap() (из курса "Принципы реактивного программирования" Эрика Мейера):

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.

Ваша задача: Выполнить операции над фактическими значениями (слева), преобразовав элементы типа T union null в тип U union null, используя функцию в голубом поле (функция голубого прямоугольника). Здесь показан только один блок, но может быть цепочка голубых блоков (таким образом, переход от типа U union null к типу V _union null к типу W union null и т.д.)

Практически это заставит вас беспокоиться о null значениях, появляющихся в цепочке приложения функции. Уродливый!

Решение. Превратите T в Optional<T>, используя функции светло-зеленого поля, перейдя к дополнительным параметрам (справа). Здесь преобразуйте элементы типа Optional<T> в тип Optional<U> с помощью функции красного поля. Отражая применение функций к фактическим, может быть несколько функций красного ящика, которые нужно связать в цепочку (таким образом, переходя от типа Optional<U> к Optional<V>, затем к Optional<W> и т. д.). В конце вернитесь от дополнительных параметров к фактическим с помощью одной из функций темно-зеленого поля.

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

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

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

Простые.

Необязательная Java, как ее использовать

person David Tonhofer    schedule 29.10.2020