* Генератори на функции (Currying) в Java 8
Публикувано на 14 март 2015 в раздел ПИК3 Java.
Ако имаме една функция от няколко входни параметри, която като резултат връща друга функция с по-малко параметри, ще казваме, че имаме "генератор на функции". Терминът на английски се нарича "currying" и е важен за функционалното програмиране. Нека разгледаме един пример за генератор на функция, който е написан на по-стара версия на Java (<=7). Нарочно няма да използваме функционалните интерфейси, които са налични в Java 8, а ще си дефинираме сами такъв:
public class CurryExample{ // Интерфейс за функция на два аргумента връщаша Result // Използват се генерични типове interface Func<Arg1, Arg2, Result>{ Result apply(Arg1 a, Arg2 b); } // Интерфейс за функция на един аргумент връщаща Result interface CFunc<Arg, Result>{ Result apply(Arg a); } public static void main(String[] args){ // Стандартна функция за събиране на две числа Func<Integer, Integer, Integer> notCurriedAdd = new Func<Integer, Integer, Integer>(){ public Integer apply(Integer a, Integer b){ return new Integer(a.intValue()+b.intValue()); } }; // Събиране на две числа чрез генератор на функция CFunc<Integer, CFunc<Integer, Integer>> curriedAdd = new CFunc<Integer, CFunc<Integer, Integer>>(){ public CFunc<Integer, Integer> apply(Integer a){ return new CFunc<Integer, Integer>(){ public Integer apply(Integer b) { return new Integer(a.intValue()+b.intValue()); } }; } }; // Демонстрираме стандартното събиране System.out.println(notCurriedAdd.apply(1, 2)); // Събиране чрез генератора на функции System.out.println(curriedAdd.apply(1).apply(2)); } }
Виждате, че идеята за реализиране на генератор на функция (в Java естествено това е просто симулирано - реално се генерират обекти) е да се използват генерични типове и така метод на интерфейс да приеме като аргумент параметър от тип същия интерфейс и да го използва за връщан резултат.
Този начин за дефиниране на генератори на функции е възможен, но е видимо неудобен. За щастие в Java 8 идват ламбда функциите, чрез които горния код става значително по-прост и по-лесен за четене. Ще използваме оригиналните интерфейси, които използвахме и в предишния пример, въпреки че можем спокойно да използваме вградения функционален интерфейс Function:
public class CurryExample{ interface Func<Arg1, Arg2, Result>{ Result apply(Arg1 a, Arg2 b); } interface CFunc<Arg, Result>{ Result apply(Arg a); } public static void main(String[] args){ Func<Integer, Integer, Integer> notCurriedAdd = (a, b) -> new Integer(a.intValue()+b.intValue()); CFunc<Integer, CFunc<Integer, Integer>> curriedAdd = a -> b -> new Integer(a.intValue()+b.intValue()); // Демонстрираме стандартното събиране System.out.println(notCurriedAdd.apply(1, 2)); // Събиране чрез генератора на функции System.out.println(curriedAdd.apply(1).apply(2)); } }
Какво печелим от генераторите на функции? С тях можем да правим "частични операции" (partial operations). Например можем да направим следното:
CFunc<Integer, Integer> partialAdd = curriedAdd.apply(1);
Тоест извършили сме само първата част от нашата операция "събиране". Ще наричаме такава операция "междинна" (intermediate). Извършването на крайната операция, т.е. partialAdd.apply(2), ще бъде "крайна" (terminal) операция. Забележете също, че при извършване на currying ние извършваме и затваряния (closures). В curriedAdd от горния код вие не можете да промените референциите на "a" и "b" - ще получите вече добре познатото съобщение за грешка "Error: local variables referenced from a lambda expression must be final or effectively final".
Генераторите на функции са основната техника за симулиране на функционално програмиране в Java. Пример за такова са т.нар. монади (Stream), които ще разгледаме в следваща статия.
Добави коментар