Pull to refresh

Comments 143

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

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

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

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

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

def multN(value) : return (lambda x : x**value)
class MultN(object):
    def __init__ (self, power):
        self.power = power

    def __call__ (value):
        return value ** self.power
А так?

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

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

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

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

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

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

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

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

return Functions.componse(fn1, fn2);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Как 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?
first class function означает, что функции могут быть аргументами функций и их возвращаемыми значениями. Function все это обеспечивает.
это делегаты, не замыкания
есть в совсем не функциональных паскале и си, например

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


то есть, если у вас дома нету интернета и нет желания учить фп — вот пожалуйста вам в наказание, горите в аду!!!
Sign up to leave a comment.

Articles