Функциональное программирование в Java

Эта статья о:
  • О применении функционального стиля программирования в языке Java.
  • О некоторых базовых паттернах для работы с коллекциями данных из функционального программирования в примерах на Java.
  • Немного о библиотеке Google Collections.

Если вы программируете на языках Java, C#, C++, PHP, или любом другом ОО языке, хотели бы познакомиться с функциональным программированием, но не имеет возможности/желания изучать Haskell/Scala/Lisp/Python, — эта статья специально для вас.

Тем, кто знаком с функциональным программированием, но никогда не применял его в Java, думаю, это будет тоже интересно.


Вводим конструкцию «функция» в языке Java


Что такое функциональное программирование? Если в двух словах, то функциональное программирование — это программирование, в котором функции являются объектами, и их можно присваивать переменным, передавать в качестве аргументов другим функциям, возвращать в качестве результата от функций и т. п. Преимущества, которые раскрывает такая возможность, будут понятны чуть позже. Пока нам надо разобраться, как в Java можно использовать саму конструкцию «функция».

Как известно, в Java нету функций, там есть только классы, методы и объекты классов. Зато в Java есть анонимные классы, то есть классы без имени, которые можно объявлять прямо в коде любого метода. Этим мы и воспользуемся. Для начала объявим такой интерфейс:

public final interface Function<F, T> {
    T apply(F from);
}


Теперь в коде какого-нибудь метода мы можем объявить анонимную реализацию этого интерфейса:

public static void main() {
    // Объявляем "функцию", присваиваем ее переменной intToString.
    Function<Integer, String> intToString = new Function<Integer, String>() {
        @Override public String apply(Integer from) {
            return from.toString();
        }
    };

    intToString.apply(9000); // Вызываем нашу функцию. Получаем строку "9000".
}


Такую реализацию мы и будем называть «анонимной функцией». С точки зрения функционального программирования с ней можно делать все то же самое, что и с функцией из функциональных языков: присваивать переменным, передавать в качестве аргумента другим функциям(и методам классов), получать в качестве результата от функций(и методов классов).

Теперь можно перейти к изложению некоторых базовых паттернов функционального программирования.

Работа с коллекциями в функциональном стиле


Допустим, у нас есть некая коллекция целых чисел. Мы хотим их вывести на экран в виде строки, и каждое число в строке будет разделено через запятую. Нефункциональное решение выглядело бы примерно так:

public String joinNumbers(Collection<? extends Integer> numbers) {
    StringBuilder result = new StringBuilder();
    boolean first = true;
    for (Integer number : numbers) {
        if (first)
            first = false;
        else
            result.append(", ");
        result.append(number);
    }
    return result;
}


Для реализации функционального решения нам потребуется сперва подготовить несколько функций и методов. Будем объявлять их в качестве статических полей класса:

public static final Function<Integer, String> INT_TO_STRING = ... // Уже реализовали выше

// Берет поэлементно значения из коллекции from, преобразует их с помощью функции transformer
// и возвращает список результатов преобразования в том же порядке.
public static <F, T> List<T> map(Collection<F> from, Function<? super F,? extends T> transformer) {
    ArrayList<T> result = new ArrayList<T>();
    for (F element : from)
        result.add(transformer.apply(element));
    return result;
}

// Берет коллекцию произвольных элементов и конкатенирует их в строку
public static <T> String join(Collection<T> from, String separator) {
    StringBuilder result = new StringBuilder();
    boolean first = true;
    for (T element : from) {
        if (first)
            first = false;
        else
            result.append(separator);
        result.append(element);
    }
    return result.toString();
}


Теперь наш метод joinNumbers будет выглядить следующим образом:

public String joinNumbers(Collection<? extends Integer> numbers) {
    return join(map(numbers, INT_TO_STRING), ", ");
}


Метод реализован ровно в одну простую строку.

Хотелось бы отметить несколько важных моментов:
  1. Методы map и join являются достаточно обобщенными, то есть их можно применять не только для решения данной задачи. Это значит, что их можно было бы выделить в некий утилитный класс, и использовать потом этот класс в разных частях проекта.
  2. Вместо класса Collection в методе map можно было бы передавать Iterable и возвращать новый Iterable, извлекая из переданной коллекции данные по мере обхода данных в возвращаемой коллекции, то есть извлекать элементы лениво, поэтапно, а не все сразу. Такая реализация, позволит, например, создавать цепочки преобразования данных, выделяя каждый этап преобразования в отдельную простую функцию, при этом эффективность алгоритма будет оставаться порядка O(n):
    map(map(numbers, MULTIPLY_X_2), INT_TO_STRING); // каждый элемент умножаем на два и приводим к строке.
  3. Создавая какой-нибудь класс, вы можете создавать для некоторых его методов статические поля, являющиеся функциями-обертками, делегирующими вызов apply на вызов соответствующего метода класса. Это позволит использовать «методы» объектов в функциональном стиле, например, в представленных выше конструкциях.


Работа с коллекциями с помощью Google Collections



Ребята из Google как раз создали удобную библиотеку с утилитными классами, позволяющую работать с коллекциями в Java в функциональном стиле. Вот некоторые из возможностей, которые она предоставляет:
  • interface Function<F, T>. Интерфейс, аналогичный приведенному мной выше.
  • Iterables.filter. Берет коллекцию и функцию-предикат(функцию, возвращающую булево значение). В ответ возвращает коллекцию, содержающую все элементы исходной, на которые указанная функция вернула true. Удобно, например, если мы хотим отсеить из коллекции все четные числа: Iterables.filter(numbers, IS_ODD);
  • Iterables.transform. Делает то же самое, что функция map в моем примере выше.
  • Functions.compose. Берет две функции. Возвращает новую функция — их композицию, то есть функцию, которая получает элемент, подает его во вторую функцию, результат подает в первую функцию, и полученный из первой функции результат возвращает пользователю. Композицию можно использовать, например, так: Iterables.transform(numbers, Functions.compose(INT_TO_STRING, MULTIPLY_X_2));


В Google Collections конечно есть еще много других полезных вещей как для функционального программирования, так и для работы с коллекциями в императивном стиле.

Ссылки



О чем хотелось бы рассказать еще


Дорогие друзья, если вам понравилась моя статья, я с удовольствием напишу еще что-нибудь интересное о применении функционального программирования в Java и других императивных языках. Вот некоторые из вещей, о которых есть желание рассказать, но нету возможности изложить сразу в одной статье:
  1. Мутабельные и иммутабельные замыкания.
  2. Pattern-matcher.
  3. Монады.
  4. Распараллеливание с использованием функционального подхода.
  5. Комбинаторы парсеров.

Буду рад услышать ваши комментарии и предложения.
Метки:
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 143
  • 0
    Неделя извращенства на хабре ;)
    • +1
      Не извращенство. После функциональных языков такого в джаве ой как не хватает. А на работе писать можно только на джаве :)
      • +4
        Да, пишешь на С++, хочется на Java. Пишешь на Java, хочется на Scala.
        • 0
          На С++ описанные в посте конструкции уже лет 10 во всю используются, + имитация лямбд + каррирование (binding параметров). Так что скорее пишешь на Java, хочется на С++.
          • 0
            Хочется на Scala — пишешь на Asm'е :-) даешь рекурсию!
      • +4
        Вместо join, наверное «более функциональным» было бы реализовать аналог reduce, а уже join можно сделать на его основе.
        • +1
          Согласен с вами.

          Просто я хотел попроще, чтобы людям не знакомым с функциональным программированием было понятнее. Ну, и место в статье ограничено.
          • +2
            А чтобы сделать его еще более функциональным, надо было вместо аналога reduce надо было реализовать аналог foldl, на котором реализовать reduce, на котором реализовать join.
          • +1
            Pattern-matcher было бы очень интересно.
          • 0
            Python? Почему его причислили к функциональным языкам?
            • 0
              Списочные выражения
              • 0
                Списочные выражения к функциональному программированию относятся постольку-поскольку.
                • –1
                  Для питонистов это тот уровень функциональщины, который они могут принять:)
                  • 0
                    Ну я лично пишу в том числе и на питоне, однако назвать его функциональным язык не поворачивается)
                    • 0
                      Ну все-таки списочные выражения пришли из ML-языков, насколько я понимаю. Да и выглядят они в точности как описания множеств из учебника по алгебре.

                      Википедия гласит, что в Python есть следующие элементы ФП:
                      функции высших порядков, развитые средства обработки списков, рекурсия, возможность организации ленивых вычислений
                      Это немало:)
                      • +2
                        Списочные выражения были задолго до ML. Взять хотя бы Лисп. Вообще списки — базовая структура данных и в том или ином виде есть в любом высокоуровневом языке программирования.
                        И выкиньте свой учебник по алгебре, списки никак не тянут на определение множества хотя бы потому, что в них важен порядок элементов, а в множестве нет. Если точно, список — множество пар вида (i, e), где i — индекс (элемент множества с установленным отношением полного порядка), e — элемент множества-носителя,

                        list ≡ {(i, e)} | i ∈ I, e ∈ E, (a, e1) = (b, e2) & a = b => e1 = e2.
                        • 0
                          У меня создалось ощущение, что вы не знаете, что такое списочное выражение и чем они отличаются от списков.
                          • +1
                            просветите
                            • +1
                              Я бы с удовольствием, но плохо объясняю. Однако вот отрывок из учебника на Хаскелю. Автор хорошо показывает, что такое списочные выражения и откуда у них ноги растут. Обратите внимание, синтаксис очень похож на питоновский.
                              • +4
                                Спасибо, но я знаком с Хаскелем (языком, не с Карри). Списочные выражения, или, точнее, S-выражения — это способ записи, иногда — способ хранения данных. И списки как структура данных обычно реализуются так, чтобы можно было осуществить разделение «голова-хвост»
                                • +3
                                  Ваше неверное представление, видимо, возникло из-за построителей списков в Хаскеле. Так вот, списки иногда (подчеркиваю, только иногда) являются удачным способом для представления множеств. В частности, механизм построения списка основан на теореме, что множество может быть определено с помощью универсума и предиката принадлежности элемента универсума к множеству. Но любая модифицирующая операция над множествами, представленными списками, требует проверки результата на наличие повторов
                            • 0
                              И, кстати, в списках-то индекс как раз и не нужен. Нам нужна только его «голова» (ну и хвост). Это позволяет осуществлять ленивые вычисления.
                              • +1
                                А кто говорит об индексе как о физически хранимом параметре? Тут важно, что в списке важен ПОРЯДОК элементов. У вас легко один и тот же элемент может быть головой списка и при этом содержаться в его хвосте. В множестве не может быть двух одинаковых элементов по определению
                            • 0
                              > Википедия гласит, что в Python есть следующие элементы ФП:…
                              Это мало. Эти концепции можно реализовать даже на обычном Си. Однако это не сделает Си функциональным языком программирования. «Функциональность» питона проистекает из-за наличия в нем лямбда-функций, все остальное — просто следствие
                              • +1
                                Лямбды — это тупо сахар. Ничего в них мистического нет. Куда можно передать функцию, туда можно передать и лямбду, и наоборот.
                                • 0
                                  лямбды — это инструмент построения функций. Без них язык не может быть функциональным в принципе. И это не просто сахар, за ними стоит очень мощная теория
                                  • +4
                                    А вы не путаете лямбда-исчисление и понятие lambda в терминах питона? lambda в питоне — это просто анонимная функция. Что с ее помощью можно сделать такого, что нельзя с помощью именованной, покажите мне?

                                    Очень мощная теория стоит за тем, что функции являются значениями первого порядка и их можно передавать. AFAIK, это-то и имеет отношение к трудам Алонзо Чёрча.
                                    • 0
                                      Я не совсем про лямбда-исчисление Черча. Люмбда-выражения применительно к программированию — это способ построения функций «на лету». И за этим тоже стоит теория, только из области компиляции и трансляции. В питоне это действительно просто анонимная функция. Но эта функция может быть создана в процессе исполнения программы
                                      • +1
                                        Я могу сходу назвать еще два способа построения функций в рантайме в питоне, никак не вовлекающих лямбды. И что? Давайте теперь молиться на декораторы и метод __call__?
                                        • 0
                                          __call__ и декораторы — это совсем из другой степи, не имеющей отношение к функциональному программированию, и кстати не позволяют породить новые функции
                                          • 0
                                            а лямбды-то как порождают новые функции, просветите?
                                            • 0
                                              как конкретно интерпретатор Питона это делает — не знаю. Хотя ради Вас пороюсь в исходных кодах. А вот само «порождение» функции — пожалуйста:

                                              def multN(value) : return (lambda x : x**value)
                                              • 0
                                                class MultN(object):
                                                    def __init__ (self, power):
                                                        self.power = power
                                                
                                                    def __call__ (value):
                                                        return value ** self.power
                                                • 0
                                                  А так?

                                                  def fff(value) :
                                                       if value < 10 :
                                                           return (lambda x : x*2)
                                                       else :
                                                           return (lambda x y : x*y)
                                                  
                                                  • 0
                                                    Добавляем в класс соответствующие методы, используем apply.

                                                    Вы что, хотите доказать, что функтор не идентичен анонимной функции?
                                                    • 0
                                                      Я хочу показать, что есть принципиальная разница между объектом, который ведет себя по-разному в зависимости от внутреннего состояния, и функцией высшего порядка, которая производит функцию, удовлетворяющую некоторому условию
                                                      • 0
                                                        Если поле power в моем примере сделать приватным, то с точки зрения пользователя не будет разницы.
                                                        Можно импортировать вашу функцию и мой класс из разных пакетов, и без рефлексии и проверок типа вы никогда не узнаете, что есть что.
                                                      • –1
                                                        Дело не в том, что нельзя получить такую же функциональность другими методами. В большинстве случаев можно. Просто само понятие lambda было введено в Питон с целью избавиться от выражений вроде приведенного Вами. И это отнюдь не синтаксический сахар. Это совершенно другой механизм выполнения тех же действий
                                                        • 0
                                                          Я согласен, что пользоваться лямбдами в вашем синтетическом примере удобнее, чем моим классом, который куда более громоздок.

                                                          Но вы утверждали, что только лямбды позволяют порождать новые функции. Функтор, конечно, не совсем чтобы функция, но и лямбда не идентична выражению def.
                                                          • 0
                                                            Я утверждал и утверждаю, что лямбды — это не синтаксический сахар. Есть огромное количество случаев, когда применение безымянных функций «естественно», в отличие от объектов (как, собственно, в синтетическом примере). И если абстрагироваться от особенностей конкретного языка, то такое использование объектов возможно только для языков интерпретируемых, в то время как лямбды вполне хорошо живут и в компилируемых
                                                            • 0
                                                              Эмм, тут в топике, под которым мы развили этот холивар, вообще-то написано, как в компилируемом языке делать функторы. И как раз лямбд там нету (пока).

                                                              Такое использование объектов свойственно языкам, в которых есть перегрузка оператора оператора «вызов функции».

                                                              Нет, такое использование объектов свойственно только тем языкам, в которых функции не являются гражданами первого класса.
                                                              • 0
                                                                Ээээ. В каком топике? Для какого языка?
                                                                • 0
                                                                  Нажмите ctrl+home, чтобы вспомнить. Нажмите ctrl+w, чтобы забыть.
                                                                  • 0
                                                                    Может я Вам открою глаза на мир… Но Java не является компилируемым языком в том смысле, что не компилируется в машинный код
                                                                    • 0
                                                                      Теоретически может существовать железо, для которого байт-код джавы будет являться ассемблером. Ну, а пока такого железа под рукой нет, будем его виртуализировать.
                                                                      • –2
                                                                        Увы, но как-раз теоретически такого железа пока не придумано (мне теория такого железа неизвестна). Именно в перегрузке оператора вызова во время исполнения загвоздка. Можно определить N-е количество объектов с разными версиями этой функции, но нельзя получать их в процессе выполнения, это противоречит архитектуре фон Неймана. Поэтому на данный момент такая функциональность достижима только с использованием лямбда-исчисления. И именно по этой причине нет компилятора Питона. Просто нет таких технологий компиляции
                                                                        • 0
                                                                          А как же JIT?
                                                                          • 0
                                                                            JIT (Just In Time) производит исполнение, перевод в машинные инструкции «на лету». Но прямого соответствия между JIT-кодом и машинными командами для заданной архитектуры нет. То есть это все равно процесс интерпретации, хотя и более низкого уровня
                                                                            • 0
                                                                              Хм, ну тогда и компиляция — это «процесс интерпретации, хотя и более низкого уровня». Разница между компиляцией и интерпретацией зачастую только в том, что результат компиляции сохраняется в ПЗУ в виде файла.
                                                                              • 0
                                                                                Это принципиальная разница. Пока никому не удалось реализовать в железе рефлексию и интроспекцию
                                                                                • 0
                                                                                  В железе может и нет, а вот в ассемблере — запросто, мне кажется:)
                                                                              • 0
                                                                                Не правда, в Java нет никакого JIT-кода. Есть исходный байт-код, а есть машинный код, в который он компилится на основе статистики.

                                                                                И можно в байт-код, кстати, вручную скомпилировать вручную что угодно. Хотя функтор. И он скомпилится JIT-от.
                                                                                • 0
                                                                                  JIT-кода в природе нет, тут я маху дал. Это технология. И ради Бога, компилируйте в байт-код что угодно (условно), но это все равно байт код. Я говорю о том, что нет прямого соответствия между байт-кодом Java и машинным кодом процессора.
                                                                                  • 0
                                                                                    А что значит нету соответствия?

                                                                                    Не знаю, что вы подразумеваете под соответсвием, но способ компиляции байт-кода в машинный код процессора есть.

                                                                                    И вообще, ну вот напишу я на java примерно следующий код:

                                                                                    return Functions.componse(fn1, fn2);

                                                                                    Этот код создаст новый функтор и вернет. Можно с помощью ветвления задавать различные способы формирования объекта.

                                                                                    И эта функция будет транслирована в машинный код.
                                                                                    • 0
                                                                                      Эта функция будет ТРАНСЛИРОВАТЬСЯ. Нет соответствия означает, что нет (опять же, мне неизвестно) способа транслировать программу на Java в набор машинных инструкций минуя байт-код. Также как такого способа нет для Питона, Руби и многих других современных языков. Процесс выполнения Java-программы — это процесс интерпретации байт-кода Java-машиной
                                                                                      • 0
                                                                                        >способа транслировать программу на Java в набор машинных инструкций минуя байт-ко

                                                                                        А что меняет наличие этого промежуточного звена?

                                                                                        >Процесс выполнения Java-программы — это процесс интерпретации байт-кода Java-машиной

                                                                                        Это не так. Процесс выполнения java-программы, это выполнение машинного кода. Интерпретация совершается на ранних стадиях работы программы пока JIT не собрал достаточно статистики для компиляции.
                                                                                        • 0
                                                                                          Тогда где экзешники Java-программ? Байт-код, как Вы наверное знаете, очень хорошо документирован. Существует GNU-версия компилятора Java, который компилирует в машинный код, однако полной совместимости с языком добиться так и не удалось. Потому что не все так просто. Java-машина помимо сбора статистики (которая, кстати, может помочь оптимизировать процесс интерпретации, но никак не исключить его) делает еще кучу вещей, реализация которых в машинных инструкциях привела бы к необходимости добавлять исполняющую среду в каждую скомпилированную программу. Вот по этому и производится компиляция в байт-код. Потому что дальше этого сделать что-либо весьма проблемно
                                                                                          • 0
                                                                                            Exe-шники в памяти.

                                                                                            И, конечно, AOT-компиляторы добавляют рантайм к каждому exe-нику.

                                                                                            >Вот по этому и производится компиляция в байт-код.

                                                                                            Не по этому. Вполне можно рантайм как динамическую библиотеку таскать за собой. Байт-код же для кроссплатформенности и более эффективной компиляции за счет сбора статистики.
                                                                                            • 0
                                                                                              Интересно. Получаем следующую картину. Байт код эквивалентен машинному в том смысле, что может быть в него преобразован. Для большинства языков есть компиляторы в байт-код Java. То есть все языки — компилируемы! Ура товарищи, все проблемы решены!
                                                                                              • 0
                                                                                                А кто сказал, что компиляция в машинный код решает все проблемы производительности? Скомпилироваться то оно скомпилируется, просто не эффективно. Потому как из не эффективного байт-кода получается неэффективный машинный код.

                                                                                                К примеру, есть компиляторы языка scheme, которые дают настолько не эффективный исполняемый код, что он проигрывает даже некоторым интерпретаторам scheme.
                                                                                                • 0
                                                                                                  Про scheme история отдельная. Там компиляторы были столь ужасны, поскольку все основные механизмы ставились на костыли. И все же пример показательный. Даже для языка с таким простым синтаксисом, как scheme, не удалось создать достойный компилятор. Java — в десятки раз сложнее даже с синтаксической точки зрения. Поэтому я и говорю, что байт-код — это вынужденная мера. Компилятора Java в машинный код нет и не предвидится не потому что это никому не нужно. Желающих навалом. Только не удается это сделать
                                                                                                  • +1
                                                                                                    Извините, но вы какую-то ерунду пишете.

                                                                                                    > Даже для языка с таким простым синтаксисом, как scheme, не удалось создать достойный компилятор. Java — в десятки раз сложнее даже с синтаксической точки зрения.

                                                                                                    Возможность оптимизации практически никак не коррелирует с синтаксисом языка. Для scheme нету компиляторов потому же что и для питона — отсутствие декларации типов, например как в Common Lisp который, кстати, прекрасно компилируется в машинный код.

                                                                                                    >Компилятора Java в машинный код нет и не предвидится не потому что это никому не нужно. Желающих навалом.

                                                                                                    Вот это уже откровенный бред. Так, на вскидку: www.excelsior-usa.com/jet.html

                                                                                                    Популярностью особо не пользуется, так как желающих не так уж и много. JIT компиляция имеет одни единственный минус — тупеж приложения сразу после старта. Приходится приделывать всякие грелки.
                                                                                                    • –1
                                                                                                      Всего один вопрос. Что делает JVM? При запуске Java-программы один раз создается набор машинных инструкций — эквивалентная программа — и после этого JVM с чувством выполненного долга отдыхает? Потому что если нет, то речь идет об интерпретации байт-кода.

                                                                                                      > Для scheme нету компиляторов потому же что и для питона — отсутствие декларации типов

                                                                                                      Ох, если бы все было так просто…
                                                                                                      • 0
                                                                                                        > При запуске Java-программы один раз создается набор машинных инструкций — эквивалентная программа

                                                                                                        Практически да. Там есть несколько оговорок, правда.

                                                                                                        >— и после этого JVM с чувством выполненного долга отдыхает?

                                                                                                        Не отдыхает. По крайней мере осуществляет сборку мусора.

                                                                                                        • 0
                                                                                                          Тогда я неправ. И все-таки рисуемая Вами картина выглядит черезчур идеалистической. Если виртуальная машина (по-сути исполняющий механизм) оказывает какое-либо управляющее воздействие в процессе исполнения программы, то эквивалента в машинных кодах нет. А если такого воздействия нет, то непонятно, почему это называют виртуальной машиной, ведь в этом случае ее функции ограничиваются JIT-компиляцией.
                                                                                                          • +2
                                                                                                            Там фишка в том, что код, который редко вызывается не трогается JIT-ом, а интерпретируется. Ну и интерпретация врубается сразу после старта приложения, пока основная часть кода не сжитится. Управляющего воздействия вроде бы нету, по крайней мере не слышал.
                                                                                                            • 0
                                                                                                              Ну это понятно, очевидная оптимизация. Но слабо верится, что функции виртуальной машины ограничиваются вышеперечисленными. Плотнее займусь этим вопросом
                                                                                                              • 0
                                                                                                                Хотя в моем представлении куда более правдоподобной выглядит картина, кода часть программы, та часть, где это возможно, компилируется JIT-ом, а остальная, в которую входят рефлексия и описанные в топике вещи, интерпретируются в процессе исполнения. Очень сложно представить соответствующий таким штукам набор машинных команд
                                                                                                • 0
                                                                                                  Есть принципиальная разница между интерпретатором и рантаймом, Вам не кажется? Питон тоже можно таскать собой как библиотеку с байт-кодом, это не делает его компилируемым, потому что все равно осуществляется процесс интерпретации. В отличие от Си, в котором иногда происходит обращение к функциям библиотеки времени исполнения
                                                                                                  • 0
                                                                                                    Конечно есть. Я вообще это все писал не в защиту питона, а как доказательство того, что только реализация лямда-исчисления позволяет формировать функции в рантайме.
                                                                                                    • 0
                                                                                                      В питоне нет лямбда-исчисления. Только безымянные функции, по-старинке названные лямбдами. И все-таки такая нехитрая абстракция позволяет-таки создавать функции в процессе выполнения. Ограниченно, но позволяет
                                                                                                  • –1
                                                                                                    Соответствия нет. Когда в программе на Java присутствует многопоточность, потоки имитируются Java-машиной, а не средствами операционной системы. Когда происходит рефлексия — идет анализ байт-кода, а не машинного. Байт-код обеспечивает кроссплатформенность — бесспорно. Более эффективную компиляцию? скорее исполнение. Но не только. Это еще и вынужденная мера, позволяющая реализовать все средства языка
                                                                                                  • 0
                                                                                                    Тогда где экзешники Java-программ?

                                                                                                    Здесь. Также стоит посмотреть сюда.
                                                                                                    • 0
                                                                                                      Посмотрел. Частичная компиляция + альтернативная реализация Java-машины, не более
                                                                                    • 0
                                                                                      Подождите-ка. В C++ есть перегрузка оператора (). Вы же не скажете, что это не компилируется.
                                                                                      • 0
                                                                                        Перегрузка — процесс статический. Динамически реализовать его никак нельзя
                                                                                        • 0
                                                                                          Интересно. Буду знать. Хотя мне сложно представить такой процессор
                                                                                          • 0
                                                                                            Можно нагуглить подробности. Я не очень в процессорах разбираюсь, поэтому не стал искать, но инфа есть.
                                                                                        • 0
                                                                                          Увы, но как-раз теоретически такого железа пока не придумано (мне теория такого железа неизвестна)

                                                                                          То, что вам что-то неизвестно, ещё не означает, что этого не существует в природе: en.wikipedia.org/wiki/Java_processor.

                                                                                          Вообще, разработка процессоров специально для байт-кода высокоуровневого языка началась отнюдь не с Java. Ещё задолго до Java-машин существовали Lisp-машины и Smalltalk-машины.
                                                                                        • 0
                                                                                          было уже «практически»,
                                                                                          цитата «Компания Sun на Микропроцессорном форуме представила процессор MicroJava 701. Это первый процессор, непосредственно исполняющий байт-код Java.»
                                                                                          источник — www.osp.ru/cw/1997/42/25003/
                                                                                          («Computerworld Россия», № 42, 1997 )
                                                                                          • 0
                                                                                            Да, я ниже уже отписал об этом.
                                                                                  • 0
                                                                                    А именно это делает язык функциональным. Если основная единица — объект. То язык объектный. И можно говорить только об элементах других парадигм программирования
                                                                                    • 0
                                                                                      Тут ниже высказывалось мнение, что функциональные языки — это те, в которых все функции чистые. Я вот как-то с этим больше согласен.

                                                                                      Но я никогда и не утверждал, что питон — функциональный язык. Но функциональщины там довольно много, и это не только лямбды и списочные выражения.
                                                                                      • –1
                                                                                        Чистота функций — следствие, не причина. Списочные выражения — тоже. Парадигму составляют не какие-либо технологии, а система базовых установок, если угодно — идея. Хаскель — функциональный язык, однако писать на нем можно без использования списочных структур и с использованием изменяемых состояний. А вот лямбда-выражения — почти краеугольный камень
                                                                          • 0
                                                                            Кстати, apply позволяет сделать каррирование, что, в общем-то, порождает новую функцию.
                                                                            • 0
                                                                              Об этой возможности не знаю, напишите пожалуйста поподробней. Хотя сути в общем-то не меняет. Каррирование просто частный случай. Например в Хаскеле оно определяется очень просто через те же лямбды:

                                                                              carry f (x, y) = \x y -> f (x, y)
                                                                              • 0
                                                                                Тут я погорячился малость, простите — все-таки apply в питоне возвращает не функцию, а результат выполнения.
                                                                        • 0
                                                                          И да, рыться не надо, спасибо:) Мне не интересна в данном случае конкретная реализация.
                                                                  • 0
                                                                    Способность строить функции на лету — это свойство рантайма. Лямбда функции — это просто один из синтаксисов, позволяющих этим свойством воспользоваться, вам тут правильно написали.
                                                                    • 0
                                                                      Тогда всё — способности рантайма. Любая возможность языка — просто свойство рантайма
                                                                      • +1
                                                                        Не только рантайма. Некоторые вещи обеспечиваются на этапе разбора исходного кода, а некоторые обеспечиваются с помощью рантайма. Никакой магической математики под капотом там нет.
                                                          • +1
                                                            Пожалуйста, поясните, почему вы считаете люмбды «тупо синтаксическим сахором», а списочное выражение нет?

                                                            Вам не кажется, что любой язык высокого это синтаксический сахар над набором машинных команд? И в чем тогда по-вашему состоит важное отличие функциональных языков программирования от других?
                                                            • 0
                                                              Списочные выражения значительно повышают выразительность кода и привносят новую семантику. Особенно вкупе с генераторами.
                                                              Лямбды — нет.

                                                              Вам не кажется, что любой человеческий язык — это синтаксический сахар над криками и жестами?
                                                              Действительно важное отличие для меня состоит в совершенно отличной семантике. Так же часто упоминают отсутствие побочных эффектов и ленивые вычисления.
                                                              • 0
                                                                По-моему, единственной важной особенностью как-раз и является отсутствие побочных эффектов, все остальное можно накалякать сверху самому. По этому когда говорят, что человек пишет в функциональном стиле на каком-то языке, имеют ввиду, что он старается минимизировать изменяемые состояния. И по этому map > for.

                                                                Питоновская лямбда в свою очередь имеет действительно другую семантику, конечно, это не честно каррированная функция, но и не просто анонимная — в ней напрямую нельзя изменять состояния (lambda: c = 5 не покатит), хотя можно изменить косвенно, по тому, что окружение не «чистое», вроде (lambda x: x.y()).
                                                                • 0
                                                                  Поясню. Допустим, мы имеем только чистые функции (без побочных эффектов, чтобы было проще возьмем только от одного аргумента, имена функций и аргументов не существенны, по этому их опустим), для того, чтобы при помощи них произвести какие-то вычисления нужно определить список операций, которые мы можем над ними выполнять. Алонзо Черч показал, что минимально необходимых операций всего 3 — α-преобрзование, β-редукция, η-преобразование.

                                                                  Имея только чистые функции и 3 операции можно провести любые вычисления (язык полный по Тьюрингу), такой язык называют λ-счислением. Например, построить операцию рекурсии (Y-комбинатор).

                                                                  Но так как каждый раз записывать Y-комбинатор или числа в Church numerals муторно и бессмысленно, то напридумали разного синтаксического сахара, например функции от нескольких аргументов (define в лисп) и тп
                                                                  Таких функций напридумывали много (map, fold, reduce и тд) и язык реализцющих их каким-то образом, но не реализующий лямда-счисления, называют языком с функциональными возможностями, которым является Python. Lisp в свое время и был реализацией лямбда-счисления, по этому он просто функциональный язык.
                                                                  • 0
                                                                    Спасибо. Я стал лучше понимать, что придумал Черч.
                                                                    • +1
                                                                      Все верно, с одним замечанием. Y-комбинатор относится к комбинаторной логике, а не к лямбда-исчислению. Хотя они и изоморфны
                                                                    • 0
                                                                      И все-таки — одно дело, когда сверху сам калякаешь, а другое, когда язык это поощряет. Любая реализация тех же списочных выражений в виде некоторой функции будет выглядеть более громоздко.

                                                                      Видимо, я был неправ, когда говорил, что лямбды в питоне не несут никакой новой семантики. Во-первых, я не знал про «lambda: c = 5 не покатит». Во-вторых, я пишу на JS, а там нет различия меду анонимными функциями и именованными (за тем исключением, что именованные подвергаются «поднятию», но я не могу придумать красивый не синтетический пример, когда это меняет их использование).
                                                                  • 0
                                                                    Или, другими словами — где та грань, где синтаксический сахар переходит в новую семантику? Как вы считаете?
                                                                    • 0
                                                                      эта грань в мозгах, а не в языке. Кто то на Java как фортране пишет, кто то на С в объектном стиле.
                                                                • –1
                                                                  А писать на питоне можно очень по разному:)
                                                        • +1
                                                          Функциональное программирование — это в первую очередь иммутабельность данных, а уж потом все манипуляции с функциями, как со значениями.
                                                          • 0
                                                            В функциональном программировании не функция является объектом, а все элементы языка могут быть интерпретированы как функции. Поэтому можно говорить только об элементах функционального программирования в Java, Python и прочив мультипарадигмальных языках
                                                            • +3
                                                              Функциональное программирование — это идея в головах людей(или если хотите, подход к решению), а не конкретный язык или набор фишек, впрочем стоит отметить, что определенный набор-таки позволяет сделать это проще и естественее.

                                                              А вообще да, как человек, который пишет на java и функциональных языках, могу сказать, что редко мешаю стандартные вещи из Java с идеями подчерпнутыми из функциональной среды — поэтому было бы невероятно интересно увидеть продолжение.

                                                              мое имхо, что стоить иметь хороший набор методик из разных источников и парадигм, чтобы выбрать что-то лучшее для решения задачи, но в реальной жизни иструменты довольно сильно ограничены, поэтому написанное выше отличный компромис между форсированными сверху инструментами и выбором идей для решения задач.
                                                              • 0
                                                                Спасибо за хороший отзыв. Буду работать над продолжением. :)

                                                                > мое имхо, что стоить иметь хороший набор методик из разных источников и парадигм, чтобы выбрать что-то лучшее для решения задачи

                                                                Стараюсь придерживаться того же подхода.
                                                              • –1
                                                                Все попытки разбавить императивный поноскод на Java какими-либо костылями приводят в ужас. Приведенный в этом посте map имеет очень печальную, неленивую реализацию как по выделению памяти, так и по выполнению. Дальше читать уже не имело смысла.
                                                                • 0
                                                                  В Google Collections метод Iterables.transform — ленивый.
                                                                • 0
                                                                  Все-таки с functionaljava.org/ это будет получше чуток
                                                                  • 0
                                                                    Со всем уважениемм и благодарностью за проделаную работу.

                                                                    Как Function<F, T> реализует first class function?
                                                                    А если не реализует — функциональное ли это программирование?

                                                                    Скажем в javascript делаем так:

                                                                    function outer () {
                                                                    var ctx="внутри";
                                                                    var inner = function() {
                                                                    return ctx;
                                                                    };
                                                                    return inner;
                                                                    }

                                                                    var ctx="снаружи";
                                                                    var res = outer();
                                                                    console.debug(res());



                                                                    Как это реализуется на Java?
                                                                    • –1
                                                                      first class function означает, что функции могут быть аргументами функций и их возвращаемыми значениями. Function все это обеспечивает.
                                                                      • –1
                                                                        это делегаты, не замыкания
                                                                        есть в совсем не функциональных паскале и си, например

                                                                        замыкание, иже closure или first class function, должно еще уметь таскать за собой контекст, что и делает функциональщину такой приятной и полезной на асинхронных алгоритмах

                                                                        и Function<F, T> из примера, строго говоря, контекст тащит, хоть и некими ограничениями
                                                                        но я не пойму почему в статье об этом ни слова
                                                                        • –1
                                                                          >есть в совсем не функциональных паскале и си, например

                                                                          А по-вашему в не функциональных языках не может быть элементов функциональщины? Ссылка не процедуру в Си вполне себе элемент функциональщины.

                                                                          А замыкание, это замыкание. Понятие ортогональное first class function. И если вам интересно, то в Java они с оговоркой есть. Можно внутри анонимного класса обращаться к final ссылкам и полям внешнего объекта.
                                                                          • 0
                                                                            Ссылка на процедуру в Си вовсе не есть элемент функциональщины. По этому вопросу, собственно, и все дебаты. Как было правильно замечено ранее, функциональное программирование не определяется набором возможностей языка, это образ мышления. Если функцию можно рассматривать в математическом смысле, т.е. как отображение одного множества на другое, то это — функциональный язык. Поэтому Си — не функциональный, а императивный язык программирования
                                                                            • 0
                                                                              >Если функцию можно рассматривать в математическом смысле, т.е. как отображение одного множества на другое, то это — функциональный язык.

                                                                              И что же нам мешает это делать в Си, тем более вы говорите, что ФП-это идея?

                                                                              > Поэтому Си — не функциональный, а императивный язык программирования

                                                                              При чем здесь «поэтому»? Си процедурный язык, так как он поощряет такую парадигму. И никак не запрещает другие, например объектно-ориентированную (см. GObject), метаязыковую (привет макросам) или функциональную (опять же привет указателям на процедуры).
                                                                              • –1
                                                                                Где в Си наследование, инкапсуляция и полиморфизм, чтобы его можно было считать объектным? Макросы там определяют процесс компиляции и только, к алгоритмике не имеют никакого отношения. И когда Вы сможете на Си сделать то же каррирование — тогда можно будет поговорить о функциональности. А так, простите, процедурный язык и только
                                                                                • 0
                                                                                  >Где в Си наследование, инкапсуляция и полиморфизм, чтобы его можно было считать объектным?

                                                                                  Читайте документацию по GObject.

                                                                                  >Макросы там определяют процесс компиляции и только, к алгоритмике не имеют никакого отношения.

                                                                                  А при чем здесь алгоритмика? Речь идет о кодогенерации

                                                                                  >И когда Вы сможете на Си сделать то же каррирование — тогда можно будет поговорить о функциональности

                                                                                  Извините, говорить о чем? Я вам говорю, что ссылки на процедуры — элемент функционально программирования. Маленький такой, куцый, но элемент.

                                                                                  >А так, простите, процедурный язык и только

                                                                                  А я разве доказываю обратное?

                                                                                  • 0
                                                                                    Я так понимаю, у нас с Вами разные исходные убеждения. Для меня неприемлемо говорить о поддержке какой-либо идеи даже элементно, если не отражены основные концепции. То, что в Си можно присваивать переменной адрес функции, не является элементом функционального программирования, потому что сама концепция присваивания функции, именно функции, чужда этому языку. Речь может идти о присваивании адреса, что можно реализовать и на ассемблере. И я не думаю, что найдется человек, всерьез утверждающий, что ассемблер поддерживает функциональную парадигму программирования
                                                                                    • +1
                                                                                      Да я, расцениваю все с чисто практической точки зрения. Если можно добиться сходных результатов — это «оно».
                                                                      • 0
                                                                        ta6aku, спасибо за конструктивную критику.

                                                                        В моей статье действительно ничего не было сказано о замыканиях, хотя это один из ключевых элементов функционального программирования. Уверяю вас, что замыкания в Java можно использовать также как и в JavaScript. Наверное в следующий раз стоит начать статью именно с этого вопроса.
                                                                      • +2
                                                                        А где тут функциональное программирование?
                                                                        • 0
                                                                          Вот тут вижу выше много споров о том функционально/не функционально. Товарищи спорщики, вы бы вначале договорились о том, что такое функциональное программирование, а потом уже спорили. Я когда разбирался с этой терминологией и пришел к выводу, что определение у термина очень размытое. Есть некий набор свойств: функции как первоклассные значения, immutable данные, рекурсия, паттерн матчинг, алгербраические типы данных и так далее. При этом никакой набор признаков нельзя назвать необходимым или достаточным.
                                                                          • –1
                                                                            eliah_lakhin, Если возможно, расскажите, в чём преимущества «функциональной» реализации по сравнению с «традиционной». Просто из примера, это не очевидно: кода стало больше + синтаксис generic'ов усложняет чтение.
                                                                            • 0
                                                                              Одно из преимуществ, например, заключается в том, что метод main, изначально написанный в императивном стиле(«традиционном»), занимал 6 строк, а после переписывания в функциональном стиле стал занимать всего одну строку.
                                                                              • 0
                                                                                main? Насколько я понял, он состоит только из вызова joinNumbers и для мало что изменилось при переходе к функциональной реализации.

                                                                                А вот кода стало больше (INT_TO_STRING + map +join > императивный joinNumbers)

                                                                                И появились конструкции вроде
                                                                                public static <F, T> List map(Collection from, Function<? super F,? extends T> transformer) {
                                                                                • 0
                                                                                  > А вот кода стало больше (INT_TO_STRING + map +join > императивный joinNumbers)

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

                                                                                  А можно вообще ничего не писать, просто взять уже готовый Google Collections или Apache Common Collections.
                                                                            • +1
                                                                              Странно, что никто не откомментировал про Clojure. Функциональное программирование без всяких извращений.
                                                                              • +1
                                                                                А что про него комментировать? Котлеты отдельно, мухи отдельно. Являясь большим фанатом clojure, я пишу участки, требовательные к производительности, на Java. И в Андроид пока clj не засунешь (я пробовал — 7 секунд на запуск пустого приложения). Если сравнить jvm-байткод с ассемблером, то Java сейчас — это С. Есть множество более экспрессивных и concise языков (Scala/JRuby/Groovy/Clojure), но что должно работать быстро, все равно пишется на Джаве.
                                                                                А вот-такие штуки для Джавы вроде Google Collections — это как С++ как С. Код выглядит как дерьмо, но зато можно сделать больше, не теряя производительности.
                                                                                • 0
                                                                                  Прошу прощения, конечно же «это как С++ для С».
                                                                              • 0
                                                                                Давно было интересно, как можно писать функционально на Java. Однако эта статья не ответила на основные мои вопросы, которые возникают сразу после объявления интерфейса Function:

                                                                                1) Как реализуется композиция: λxyz.x(yz)?
                                                                                2) Типы (которые всё-таки вводятся, т.к. интерфейс-то параметрический), как я понимаю, образуют категорию, допускающую экспоненцирование и умножение. Как реализовать контроль типов? Более точный вопрос: как выглядит функция каррирования, ведь для этого нужно как минимум определить произведение?
                                                                                С экспоненцированием проблем таких нет, т.к. Function, будучи интерфейсом, может рассматриваться как тип аргумента/результата (что, собственно, и подчеркивается в статье)
                                                                                • 0
                                                                                  1) В терминах Google Colletions композиция может быть реализована с помощью Functions.compose. Она принимает две функции и создает инстанс новой функции, которая при вызове осуществляет применение(Function.apply) переданных функций.

                                                                                  2) Каррирование можно реализовать, например, одним из двух способов:

                                                                                  Способ первый. Объявить порядка семи-восьми функций, принимающих разное количество аргументов, и имеющих разный тип(Function0 — от нуля аргументов, Function1 — от одного аргумента и т.п.). Для каждого типа функций объявить метод apply с меньшим числом аргументов, который будет возвращать каррированную функцию.

                                                                                  Способ второй. Объявить гетерогенные коллекции(кортежи, «параметрические бины»), опять таки, для разного количества аргументов. Ну и сделать методы каррирования для функций от таких аргументов.

                                                                                  Отмечу, что в обоих случаях можно сделать наследование между функциями и/или кортежами, то есть, например, кортеж от пяти аргументов — это частный случай коллекции от трех аргументов. Это позволит немного унифицировать работу с функциями разных типов. Также в обоих случаях мы имеем ограничение на число аргументов, правда передавать больше шести-семи аргументов тоже не очень хорошо(в Haskell, насколько я помню, гетерогенные коллекции тоже ограничены по длине).

                                                                                  В общем, оба способа на самом деле не универсальны, имеют свои плюсы и минусы, однако в целом позволяют решить проблему типобезопасным образом.
                                                                                • 0
                                                                                  Вы видели Scala?
                                                                                  Там все проще делается, без попыток заставить императвную Java быть функциональной.
                                                                                  • 0
                                                                                    Конечно видел. Я на нем пишу каждый день, и не только на нем. :)
                                                                                    • 0
                                                                                      Тогда непонятно желание протащить это на Java. Разве что для развлечения :).
                                                                                      Кстати очень напомает linq в C#.
                                                                                      • +3
                                                                                        Статья ориентирована на Java программистов, которые хотели бы познакомиться с ФП. Мне кажется, что примеры на Java конструкций, имеющих аналог в функциональных языках, облегчит задачу перехода к функциональному программированию.

                                                                                        А потом, иногда бывает, что в силу обстоятельств возможности перейти с Java на что-то другое просто нету, а желание использовать функциональные конструкции есть. Конечно эти конструкции в Java получаются гораздо более громоздкими, чем, скажем, в Scala, но все-таки для решения некоторых задач могут быть полезнее и лаконичнее аналогичных решений в императивном стиле.

                                                                                        Ну, и да, конечно для развлечения тоже. :)
                                                                                  • +1
                                                                                    Пользуюсь Google Collections как раз для этих целей. Очень удобно.
                                                                                    • 0
                                                                                      Пользуюсь иногда Guava, и каждый раз когда идет порыв на использование transform, щелчок в мозгу — кода столько же, засирается PermGen, а результат один. В итоге пишу обычный цикл.

                                                                                      Цееность таких извратов в стандартной яве для меня сомнительна. Как когда то писали не помню кто — если хотите писать на паскале, пишите на паскале, а не делайте макросы begin и end
                                                                                      • +1
                                                                                        Иногда(не всегда, конечно) функциональный подход может оказаться лучше в плане дизайна. Например, он позволяет облегчить задачу разделения системы на отдельные компоненты для дальнейшего их переиспользования.

                                                                                        > кода столько же

                                                                                        • 0
                                                                                          ну с этим я согласен, просто в джаве это не получается так элегантно и красиво — пока что. Вот выйдет восьмерочка с лямбдами — заживем. Там тоже не так красиво как в рубях или лиспе, но код будет короче, а значит и профит тоже хоть какой то будет (с коментом ниже я тоже согласен — очень удобно, пожалуй это один из немногих случаев когда я смело использую гуаву).
                                                                                        • 0
                                                                                          > кода столько же

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

                                                                                          хотели бы познакомиться с функциональным программированием, но не имеет возможности/желания изучать Haskell/Scala/Lisp/Python, — эта статья специально для вас.


                                                                                          то есть, если у вас дома нету интернета и нет желания учить фп — вот пожалуйста вам в наказание, горите в аду!!!

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