it-swarm.com.ru

Монады с 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);
    }
74
NimChimpsky

Просто к вашему сведению:

Предлагаемый JDK8 Необязательный class удовлетворяет трем законам Монады . Вот Gist демонстрирует это.

Все, что нужно для монады, - это предоставить две функции, которые соответствуют трем законам.

Две функции:

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

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

    • Возможно Haskell: >>= (он же bind)
    • Опция Scala: flatMap
    • Опция функциональной Java: flatMap
    • JDK8 необязательно: flatMap

Пожалуйста, смотрите выше Gist для демонстрации 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 использовать этот стиль цепочечные монадические вычисления.

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

_/Обновление: Блог об этом здесь .

74
ms-tg

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 */ }

Поэтому, хотя мы могли бы использовать методы Iterable's Iterator (hasNext и company) в цикле for старого стиля, Java предоставляет нам этот синтаксический сахар как особый случай.

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

Так -

  1. Каковы хорошие примеры монадических классов?
  2. Как будет выглядеть синтаксический сахар для работы с ними?

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

Монады часто служат классами container (например, списки). У 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 понимания.

Таким образом, List в Scala - это монада, поскольку она подчиняется законам монады Scala, согласно которым все реализации монады должны иметь соответствующие методы flatMap, map и filter (если вас интересуют законы, запись в блоге «Monads is Elephants» содержит лучшее описание, которое я нашел до сих пор). И, как вы можете видеть, лямбды (и HoF) абсолютно необходимы, но не достаточны, чтобы сделать подобные вещи полезными на практике.

Есть еще куча полезных монад, кроме контейнерных. У них есть все виды приложений. Моим фаворитом должна быть монада Option в Scala (монада Maybe в Haskell), которая представляет собой тип-обертку, обеспечивающий null безопасность: страница 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

Беглый поиск в Google показывает, что в Java есть хотя бы одна попытка сделать это: https://github.com/RichardWarburton/Monads-in-Java -

К сожалению, объяснить монады в Java (даже с лямбдами) так же сложно, как объяснить полномасштабное объектно-ориентированное программирование в ANSI C (вместо C++ или Java).

55
Faiz

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

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

В любом случае, я сообщаю вам, что я только что реализовал монаду state, используя новые 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 (aka return) и метод bind (aka flatMap). Лично я предпочитаю указывать unit как static, тогда как bind является членом экземпляра.

В случае государственной монады единица должна быть следующей:

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

в то время как связывание (как член экземпляра):

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);
    });
}

Вы заметите, что связывание должно вводить универсальный тип 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>

9
Marco Faustinelli

Вот что такое монады, что трудно понять: монады - это шаблон, а не определенный тип. Монады - это форма, они абстрактны Интерфейс (не в смысле 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 является шаблоном generic для nested инкапсуляции потока управления . I.e. способ создания повторно используемых компонентов из вложенных императивных идиом .

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

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

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

4
user2418306

единственный способ понять монады - это написать несколько библиотек комбинаторов, заметить возникающее в результате дублирование, а затем обнаружить для себя, что монады позволяют исключить это дублирование. Понимая это, каждый строит некоторую интуицию для того, что такое монада ... но эта интуиция не та вещь, которую вы можете общаться с кем-то другим напрямую - кажется, что каждый должен пройти тот же опыт обобщения на монады из некоторого конкретного примеры библиотек комбинаторов. тем не мение

здесь я нашел некоторые материалы, чтобы узнать Mondas.

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

codecommit

james-iry.blogspot

debasishg.blogspot

4
Morteza Adi

Этот пост в блоге дает пошаговый пример того, как можно реализовать тип (интерфейс) монады в Java, а затем использовать его для определения монады Maybe, как практического приложения.

Этот пост объясняет, что в язык Java встроена одна монада, подчеркивая тот факт, что монады встречаются чаще, чем могут подумать многие программисты, и что кодеры часто непреднамеренно изобретают их .

2
Katie J. Ots

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

0
Werner Thumann

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

 

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

Eric-Meijer-flatMap

0
Miguel Gamboa