Groovy: Comparator closure & currying

Рубрика: Development, Groovy | 12 May 2008, 22:41 | Vadim Voituk

Есть в Groovy такая возможность, как currying (каррирование что-ли?) замыканий.
Само понятие прийшло из функционального программирования и означает преобразование функции с один количеством агрументов, в функцию меньшего количества путем “фиксации” значений некоторых аргументов.
Например есть функция f(x,y,z), тогда её каррированием по фиксированному значению арргумента x=10 будет функция Λ(f)=f(10, y, z).
Термин currying прозшел от имени известного математика Хаскеля Карри, и ещё иногда называется  Шейнфинкелизацией (от имени Украинского математика Моисея Шейфинкля, который изобрел это понятие)

Покажу небольшой пример того, как можно использовать каррирование замыканий в Groovy.

Предположим есть обычные POGO класс:

[java]

class User {
    String name
    String emailAddress
    Date dateOfBirth
    int weight

    String toString() {
        name
    }
}

[/java]

И стоит задача реализовать сортировку списка обьектов этого класса по каждому из свойств (name, emailAddress, dateOfBirth, weight).
Для выполнения сортировки списка можно использовать метод sort(), в какой необходимо передать свой экземпляр Comparator-а.
Решением задачи “в лоб” будет реализация для каждого из сортируемых свойств отдельного Comparator-а.

Например для свойства name он будет выглядеть приблизительно так:
[java]

public class UserNameComparator implements Comparator<User> {

    public int compare(User a, User b) {
        def aValue = a.getName();
        def bValue = b.getName();

        if (!aValue && !bValue)
            return 0;
        else if (!aValue)
            return -1;
        else if (!bValue)
            return 1;
        else
            return aValue.compareTo(bValue);
    }

}

[/java]

И так 4 раза (с)… Не много ли кода для такой мелочи как сортировка?

Теперь же попробуем реализовать подобный функционал с помощью замыканий Groovy.
Создаем такое вот универсальное замыкание-компаратор (а может функцию 3х аргументов?):

[java]

def comparator = { attribute, a, b ->
    def aValue = a.getProperty(attribute)
    def bValue = b.getProperty(attribute)

    if (!aValue && !bValue)
        return 0
    else if (!aValue)
        return -1
    else if (!bValue)
        return 1
    else
        return aValue.compareTo(bValue)
}

[/java]

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

[java]
usersList.sort( comparator.curry(“name”)  )
usersList.sort( comparator.curry(“emailAddress”)  )
usersList.sort( comparator.curry(“dateOfBirth”)  )
usersList.sort( comparator.curry(“weight”)  )
[/java]

Вот так все просто и элегантно – немного функционального программирования, немного meta-программирования, немного duck typing-а.

Ссылки:
О понятии Currying в Wikipedia.
Англоязычная версия этой заметки в блоге MustardGrain
Большинство языков программирования которые поддерживают замыкания поддерживают и каррирование:
Currying в Scala (функциональном языке для JVM), Currying в Python, Currying в Ruby.

Комментариев: 13

13 Responses to “Groovy: Comparator closure & currying”

Комментарии:

  1. juriy

    Есть такая штука. Но задачу можно решить еще проще. Метод sort класса List принимает на вход замыкание, результат которого и будет использоваться как “вес” соответствующего элемента:

    [java]
    def list = []

    list << new User (name: “John”, emailAddress : “john@mail.com”, dateOfBirth:new Date(), weight:90)
    list << new User (name: “Jane”, emailAddress : “jane@mail.com”, dateOfBirth:new Date(), weight:50)
    list << new User (name: “Pete”, emailAddress : “pete@mail.com”, dateOfBirth:new Date(), weight:83)

    list.sort {it.name}
    println list

    list.sort {it.emailAddress}
    println list


    [/java]

  2. Kefir

    Спасибо, наконец то я понял зачем это нужно :)

  3. Роман Чепляка

    Маленькая поправка: математика звали Шейнфинкель (без “т”), а шейнфинкелизация — скорее шутка, чем термин.

  4. Vadim Voituk

    Роман, спасибо исправил.

  5. Chabster

    C++. Даже не буду убеждать, что он непобедим.

    #include “stdafx.h”
    #include
    #include
    #include
    #include
    #include
    #include

    using namespace std;
    using namespace boost;
    using namespace boost::gregorian;

    struct User
    {
    string name;
    string email;
    date dateOfBirth;
    int weight;

    User(const string &name, const string &email, const date &dateOfBirth, int weight) : name(name), email(email), dateOfBirth(dateOfBirth), weight(weight) { }
    };

    int _tmain(int argc, _TCHAR* argv[]) {
    vector users;
    users.push_back(User(“John”, “john@mail.com”, date(2008, 1, 2), 90));
    users.push_back(User(“Jane”, “jane@mail.com”, date(2008, 3, 4), 50));
    users.push_back(User(“Pete”, “pete@mail.com”, date(2008, 5, 6), 83));
    random_shuffle(users.begin(), users.end());
    sort(users.begin(), users.end(), bind(&User::dateOfBirth, _1) > bind(&User::dateOfBirth, _2));
    return(0);
    }

  6. Chabster

    Покоцались кавычки в инклудах и vector<User> users;

  7. dimon

    Chabster, спасибо. Крайне интересно!
    А как Ваш код использовать для сортировки на моей web-странице?

  8. Chabster

    За 50$/hour я лично расскажу и покажу.

  9. μøʝøנøʝø

    Спасибо за пример про кёриинг, только поставленная в примере задача куда проще и корректней решается имплементацией интерфейса Comparable.

Leave a Reply