Erlang декораторы

    Иногда не хватает в Erlang этой интересной особенности, вот и захотелось мне в заиметь эту фичу.

    Поискав по просторам интернета наткнулся на статью.

    Автор практически полностью реализовал весь функционал кроме передачи аргументов декоратору.

    Сразу дам ссылку на Github.

    Итак, отличие моего проекта — декоратору можно прередать праметры и опцию verbose, благодаря которой 3-м аргументом в декоратор будет передан кортеж с именем функции и строкой в файле.

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

    -module(memoize).
    
    % This row is required for decorators
    -compile([{parse_transform,decorators}]).
    
    % exporting decorator function
    -export([memoize/4]).
    
    % api exports
    -export([fact/1]).
    
    % pretty decorator usage
    -define(MEMOIZE, -decorate({?MODULE,memoize,[?MODULE],verbose})).
    
    memoize(F,Args,{FunName,_Line},Module)->
        case ets:info(memoize) of
            undefined ->
                ets:new(memoize,[public,named_table]);
            _->
                ok
        end,
        case ets:lookup(memoize,{Module,FunName,Args}) of
            [] ->
                R = apply(F,[Args]),
                ets:insert(memoize,{{Module,FunName,Args},R}),
                R;
            [{_,Value}] ->
                Value
        end.
    
    ?MEMOIZE. % decorator
    fact(N) when is_integer(N) andalso N>=1  ->
        fact(N,1).
    
    fact(1,Acc) -> Acc;
    fact(N,Acc) -> fact(N-1,Acc*N).
    


    Ну и тест 2-х вызовов:

    >> timer:tc(memoize,fact,[1000]).
    {1282,
      ... }.
    
    >> timer:tc(memoize,fact,[1000]).
    {9,
      ... }.
    


    Т.е. прирост заметен :)

    Не хочу разводить холивар на тему нужно это или нет, просто буду рад, если кому пригодится…

    P.S.
    Если кому будет интересно — могу в дальнейшем полностью описать весь процесс кодогенерации через parse_transform…
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 11
    • 0
      Недоделанный перевод (моего авторства) оригинальной статьи
      gist.github.com/2919244
      Таки так и не осмелился опубликовать таки, ибо все таки не уверен, что будет полезно в серьезных проектах.

      • 0
        На тему генерации кода, у автора англоязычной статьи есть несколько не очень удачных моментов.
        В частности, не очень удобно задавать модуль, а намного приятнее описать саму рабочую функцию декоратора через fun. Но обработка такого варианта этого «несколько» сложнее.
        • 0
          Дополнение: «обработка такого варианта» впринципе невозможна. Проблема в том, что аргументом декоратора может быть только литеральный терм. Т.е. fun там запрещен. До нашего parse_transform дело даже не найдет.
          Код типа

          -d(fun(Fun, Fargs)-> apply(Fun, Fargs) end).
          function(X)->
              X.
          

          или

          -d(fun Mod:Fun/2).
          function(X)->
              X.
          

          будет отвергнут парсером еще до применения parse_transform.
      • 0
        На тему мемоизации, то, что вы реализовали, это скорее безвременное кеширование.
        Интереснее было бы через декораторы реализовать настоящую мемоизацию.
        что-то типа
        gist.github.com/2875252
        Через Y-комбинатор. Но не сильно понятно, как на это можно навесить декораторы, так чтобы не пришлось описывать уродским не очень удобным образом функции.
        fibimpl(Self) ->
            fun
                (0) -> 1;
                (1) -> 1;
                (N) when N > 1 ->
                    ((Self)(N - 1)) + ((Self)(N - 2))
            end.
        
        • +1
          Согласен. Это скорее кэширование результата.
          Но смысл статьи просто продемонстрировать декораторы, а пример — первое что пришло в голову.

          Вообще в своем коде я декораторы применяю только для отладки участков кода, а в production они и вовсе отключены.
          • 0
            Кстати, еще одна проблема с parse_transform, в том, что ее можно отключить. Приходится быть внимательнее.
            Аналогичный вашему модуль удобно было бы использовать еще для рендеринга страниц и проверки прав доступа.

            В вашем случае каскад декораторов отработает корректно?
            • +1
              Да… И порядок имеет значение.
              Я, кстати, реализовывал loginRequired декоратор, когда писал проект под Cowboy.
              • 0
                Оно github.com/Egobrain/oauth_cowboy/blob/master/src/oauth_to_functions.erl?

                На самом деле Вы классно сделали. Таки, довели до конца задумку Ники.
                Единственное, сразу возникает желание завести что-то типа generic decorator
                и потом от него наследоваться. Вы об этом думали?
          • 0
            Разобравшись с инфой по ссылке не совсем понял какой профит принесет такой код относительно моего в том случае если декоратор поставить на рекурсивную функцию.

            fact(N) when is_integer(N) andalso N>=1  ->
                fact(N,1).
            
            ?MEMOIZE. % now decorator is here
            fact(1,Acc) -> Acc;
            fact(N,Acc) -> fact(N-1,Acc*N).
            
            • 0
              В таком случае да. Только это не математическое определение. Эффективно, но неудобно.
              Хочется писать что-то в духе (хотя это и будет дорого по памяти):
              ?MEMOIZE.
              fact(1) -> 1;
              fact(N) when N > 1 ->
                  N * fact(N - 1)
              

              Но в этом случае, бессмысленно запоминать промежуточные результаты.
              Тут это ничего не даст. Для более «сложных» примеров ваши декораторы тоже выглядят правдоподобно.
              ?MEMOIZE.
              fib(0) -> 1;
              fib(1) -> 1;
              fib(N) when N > 1 ->
                  fib(N - 1) + fib(N - 2)
              

              Ключевой момент тут, что fib(N — 2) должна использовать результаты промежуточных вычислений fib(N — 1). После вычисления целевой функции хранить промежуточные результаты смысла особого не имеет, только конечный. Во общем на то и нацелен каскад непонятных функций в примере. Если их удаться удачно убрать в декоратор, то будет здорово.
          • 0
            Ключевой момент. В вашей реализации декоратор возвращает значение. Но должен функцию. Это позволит комбинировать декораторы в рантайме.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.