Road to Nowhere

Do you know what you want?

0 notes

작지만 유용한 erlang 매크로

Proplist는 매우 편리한 파라메터 사용 방식인데 보통 이런 식이다.

foo(Props) ->
  Value = proplist:get_value(key, Props, "default"),
  io:format("~p~n", [Value]).

그런데 OTP를 하다보면 모듈의 init/1 콜백에서 proplist를 받은 다음, 값을 하나하나 꺼내서 미리 정의한 레코드를 채운 후에 모듈 내부에서는 레코드만 사용하는 경우가 많다. 이런 식으로.

-behavior(gen_server).
-record(state, {name, age}).

init(Props) ->
  State = #state{name = proplists:get_value(name, Props, ""),
                 age = proplists:get_value(age, Props)},
  {ok, State}.

이 짓, 많이 하다보니 바보같다. 필드가 열개쯤 되면 더욱 바보같다. 백번쯤 하고서야 이런걸 만들게 된다.

%% file: props_to_record.hrl
-define(PROPS_TO_RECORD(Props, Record), ?PROPS_TO_RECORD(Props, Record, #Record{})()).
-define(PROPS_TO_RECORD(Props, Record, Default),
        fun() ->
          Fields = record_info(fields, Record),
          [Record | Defaults] = tuple_to_list(Default),
          List = [proplists:get_value(F, Props, D) || {F, D} <- lists:zip(Fields, Defaults)],
          list_to_tuple([Record | List])
        end).

8줄짜리 매크로, 이제는 이것만큼 자주 쓰는 도구도 드물다. Proplist와 레코드가 차이가 좀 있어도 문제없다. 레코드에 없는 property는 무시되고, proplist에 없는 필드는 기본값으로 남겨진다.

-behavior(gen_server).
-include("props_to_record.hrl").
-record(state, {name = "", age}).

init(Props) ->
  {ok, ?PROPS_TO_RECORD(Props, state)}.

필요는 발명의 어미니라더니. 백번이 아니라 열번만에 이걸 만들 생각을 하게 되는게 발전이겠지.