Увеличиваем читаемость generic-ов – typedef для бедныхПлохо:
Хорошо:
Плохо:
Хорошо:
Этот прием отчасти решает и проблему читаемости анонимных классов с типовыми параметрами: Плохо:
Хорошо:
Увеличиваем читаемость generic-ов-2, используем вывод типов JavaПлохо:
Хорошо:
Функции ar(T... ts) и set(T... ts) в правиле 1 – из этой же оперы. Вполне можно в класс CollectionFactory положить обертки для всех конструкторов традиционных коллекций, а конструкторы Map'ов и Set'ов назвать не просто newMap/newSet, а соответственно newUnorderedMap/newLinkedMap и newUnorderedSet/newLinkedSet, тем самым сделав акцент на наличии или отсутствии требования упорядоченности и раз и навсегда избавив себя от связанных с этим проблем. Практика показывает, что этот прием, будучи тайком внедренным в общую библиотеку, приживается среди коллег легче всего :) Увеличиваем отлаживаемость этого хозяйстваВ отладчике неприятно бывает увидеть, забравшись в кишки объекту, что его класс называется FooProcessor$3$1, а у его полей с именами innerProcessor и value – класс FooProcessor$4$2 и значение "1". Поэтому следует все классы, объекты которых "уплывают" из локальной области видимости метода (а таких абсолютное большинство, согласно правилу №3), делать неанонимными и давать им осмысленные имена. Еще лучше реализовывать в классе метод toString, отображающий внутреннее состояние объекта. Это совсем недолго, а при отладке помогает неимоверно. Кроме того, избегание анонимных классов уменьшает число проблем с сериализацией. Комбинаторы высшего порядка типа AND или OR часто применяются для склеивания заранее неизвестного числа операндов. При этом в памяти создается рекурсивная структура объектов – AND(x, AND(y, And(z, AND(...)))). Такую структуру неприятно просматривать в отладчике, и еще неприятней отлаживать ее пошагово. Поэтому иногда оправдано сделать, чтобы класс AND склеивал не 2, а произвольное число фильтров, а статический factory-метод Filters.and(first, second) (вы ведь не забыли абстрагировать в него конструктор, не так ли? ;) ) проверял, не является ли first или second уже и так AND'ом и, по возможности, склеивал их в один большой AND. Тогда рекурсивная структура превратится в итеративную и будет одно удовольствие смотреть ее в отладчике, сериализовать в XML и шастать отладчиком по циклу над слагаемыми. Теперь тяжелая артиллерия, имеющая отношение собственно к ФП: Прочитайте J vocabulary......и поймите, что делают примитивные функции и комбинаторы. Применять необязательно, достаточно просто понять, что они делают.http://www.jsoftware.com/help/dictionary/vocabul.htm. J – уникальный пример функционального языка без статической типизации и даже без замыканий. По сути, это означает, что большинство комбинаторов J можно реализовать и использовать и на Java, не скатываясь на анонимные классы. Мои любимые комбинаторы - "&." (f &. g = f-1 . g . f), "/." (x f/. y = вектор значений f(g) для каждой группы g вектора x по ключу y) и "/:" (x /: y - вектор x, отсортированный по вектору y). Карринг по последнему аргументуФункцию от нескольких аргументов можно интерпретировать как ответ на вопрос «Как Y зависит от x1, x2, …, xn?». Можно зафиксировать некоторые из xi и получить, например, ответ на вопрос «Как Y зависит от x2, x3, .., xn, если x1 = 5?» - получится функция от меньшего числа аргументов. Этот прием – фиксация некоторых аргументов – называется «карринг» и широко используется в функциональных языках. Можно сказать, что функция от n аргументов – это функция от одного (первого) аргумента, возвращающая функцию от n-1 последующих. Обычно поддерживается фиксация нескольких первых аргументов функции – например, в случае функции plus(x, y) от двух числовых аргументов можно зафиксировать первый: plus(5) – и получится функция от одного аргумента y: plus(5)(y) = plus(5, y). Вот более полезный пример: у функции matches(regex, str), проверяющей, подходит ли строка str под регулярное выражение regex, можно «закаррить» первый аргумент и получить самостоятельный «фильтратор по регулярному выражению regex», который можно сохранить в переменную, передать другой функции, применить к любой строке. Вскоре мы увидим более конкретные примеры. Java не поддерживает карринг напрямую, однако ничто не мешает воспользоваться самой идеей. В языках, поддерживающих карринг, существует «правило хорошего тона»: последний аргумент должен быть самым часто изменяющимся – тогда его будет удобно «каррить», поскольку будет иметь смысл сохранить функцию, частично примененную ко всем аргументам, кроме последнего, с тем, чтобы затем применить ее к нескольким различных значениям последнего аргумента.. Плохо:
Хорошо:
Почему: Потому что теперь комбинаторам высшего порядка есть что комбинировать – вместо «фильтров» и «отображений», которые можно лишь применить к каким-нибудь конкретным спискам, появились «фильтраторы» и «отображатели», из которых можно собирать более сложные функции:
или так:
А теперь можно писать что-нибудь такое. Пусть тип Order имеет три свойства (Customer, Product, Time) – «кто, что и когда заказал».
далее
Код mostGenerousCustomerByMonth выглядит довольно плотно, если не сказать громоздко – однако он гораздо компактнее и читаемей, чем реализация без комбинаторов высшего порядка (попытка написать или представить таковую и сравнить ее с приведенной оставляется читателю в качестве упражнения). Пары и бинарные отношенияНе так-то легко найти проект, в котором нету самописного класса Pair и не используются какие-нибудь List<Pair<Foo,Bar>>. Довольно часто возникают задачи вроде «собрать значения функции foo над левыми частями пар», или «собрать пары из значений foo на левой части и bar на правой», или «оставить только пары, у которых правая часть удовлетворяет предикату qux», и т.п. Было бы возмутительным не учредить для этого комбинаторы и не предоставить классу List<Pair<Foo,Bar>> возможность иметь их в качестве member methods:
Здесь также иллюстрируется еще один полезный прием – "worker/wrapper" (обертывание во flip : flip()/.../flip()) Теперь можно, например, написать поиск в ширину:
или преобразование Шварца (при сортировке массива по значению функции применяется для того, чтобы не вычислять функцию от одного и того же элемента несколько раз; суть – к каждому элементу в списке приклеиваем соответствующее ему значение функции, полученный список пар сортируем по второму элементу (т.е., по значению функции), и затем отбрасываем от каждой пары второй элемент.
Или вот:
И много чего другого можно написать. Вообще, flip() – очень общая и полезная функция; существует много симметричных структур – пары, бинарные отношения, обратимые функции, Map'ы... А тройные отношения можно поворачивать – TriRelation<A,B,C>.rotate213() и т.п. Но злоупотреблять этим не стоит. Крупные комбинаторыЕсть такая архитектура процессоров – VLIW, Very Large Instruction Word. В одной инструкции помещается сразу несколько действий – например, перемножить и сложить с аккумулятором; вычислить синус и косинус; и т.п.Там это делается для повышения параллелизма на уровне команд и вообще повышения быстродействия. В случае ФП на Java (и не только на Java) это можно делать для повышения читаемости и понижения количества скобок. Ну, и для быстродействия тоже – крупный комбинатор можно реализовать не через мелкие, а более эффективно (например, избавляясь от промежуточных результатов – этот прием называется deforestation, или fusion, т.е. «сплавка» нескольких комбинаторов в один). Вместо:
Делаем так:
ЗаключениеЯзык Java, с учетом возможностей Java 5, позволяет писать в значительно более читаемом стиле, нежели общепринятый, и позволяет даже использовать идеи функционального программирования. Автор надеется, что читатель найдет описанным приемам применение и испытает от их использования такую же радость. :) Source: http://rsdn.ru/article/java/JavaFP.xml | |||||||||||||||||||||
| |||||||||||||||||||||
Views: 1321 | |