Идиоматическое метапрограммирование - PullRequest
3 голосов
/ 07 декабря 2010

Хотя Интернет изобилует ресурсами, превозносящими бесчисленные возможности метапрограммирования Groovy, я еще не нашел ничего похожего на исчерпывающее руководство по «наилучшей практике» для фактического использования таких функций.

Помимо типичного предупреждения caveat о чрезмерном использовании, наиболее конкретный совет, который я прочитал, предлагает использовать категории в пользу расширения метаклассов, когда это возможно (что на самом деле является просто еще одним способом усиления старая идиома «ограниченной области»).

Здравого смысла было достаточно для моих тривиальных проектов, но я все больше беспокоюсь о том, чтобы строить из потенциально бедных / противоречивых прецедентов, когда я решаю более амбициозные задачи.

Таким образом, я был бы очень признателен за любые советы, ресурсы или конкретные примеры Groovy (или даже не зависящие от языка - мой по общему признанию краткий опыт работы с Ruby оставил меня таким же желанием) лучших методов метапрограммирования.

Чтобы прояснить тему, я приведу (очень) упрощенный проект Rational Number, который может использовать метапрограммирование несколькими различными способами:

@Immutable class Rational{
    int num, den

    Rational multiply(Integer v){
        new Rational(num:num*v, den:den)
    }
}
assert new Rational(num:1, den:2) * 3 == new Rational(num:3, den:2)

Тем не менее, попытка 3*new Rational(num:1, den:2), очевидно, приведет к исключению MissingMethodException.

Самым простым и, возможно, наименее хрупким способом добавления коммуникативного свойства был бы статический блок инициализатора в классе Rational:

static {
    Integer.metaClass.multiply = {Rational fraction -> fraction*delegate}
}
...
assert 3*new Rational(num:1, den:2) == new Rational(num:3, den:2)

Но это, следовательно, глобально, и довольно жестко.

Более универсальный и, возможно, более организованный подход был бы с некоторой опциональной начальной загрузкой:

class BootStrap{
    static void initialize(){
        Integer.metaClass.multiply = {Rational fraction -> fraction*delegate}
    }
}

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

И еще есть категории .. Безопасно явно, но не совсем удобно:

@Category(Integer) class RationalCategory{
    Rational multiply(Rational frac){
        frac*this
    }
}
use(RationalCategory){
    assert 3*new Rational(num:1, den:2) == new Rational(num:3, den:2)
}

В общем, я нахожу, что я изменяю метаклассы, когда я добавляю новое поведение, но использую категории, когда я могу изменять существующее поведение. Например, переопределение оператора деления для создания дроби будет лучше всего заключено в категории. Таким образом, решения 1 или 2 будут «приемлемыми», поскольку я просто добавляю поведение к классу Integer, а не изменяю типичное использование.

Кто-нибудь согласен / не согласен с этим мнением? Или, может быть, знаете о какой-то превосходной методологии? (Я опускаю миксины здесь, я понимаю.)

1 Ответ

1 голос
/ 08 декабря 2010

В Groovy in Action есть глава, посвященная метапрограммированию, которая довольно хорошо освещает эту тему.Обязательно получите второе издание (печатная версия еще не доступна, но вы можете получить ранний доступ к выпуску в электронном формате), поскольку первое издание очень устарело, особенно в области метапрограммирования.

...