Печать именованных параметров - PullRequest
6 голосов
/ 04 января 2009

Архейл предложил в этом посте , что написание процедуры нового формата для обработки именованных параметров может быть хорошим упражнением в обучении. Итак, в духе изучения языка я написал процедуру форматирования, которая обрабатывает именованные параметры.



Пример:

1> fout:format("hello ~s{name}, ~p{one}, ~p{two}, ~p{three}~n",[{one,1},{three,3},{name,"Mike"},{two,2}]).
hello Mike, 1, 2, 3
ok



Тест:

1> timer:tc(fout,benchmark_format_overhead,["hello ~s{name}, ~p{one}, ~p{two}, ~p{three}~n",[{one,1},{name,"Mike"},{three,3},{two,2}],100000]).
{421000,true}
= 4.21us per call

Хотя я подозреваю, что большая часть этих издержек связана с циклом, так как вызов функции с одним циклом дает ответ в <1us. </p>

1> timer:tc(fout,benchmark_format_overhead,["hello ~s{name}, ~p{one}, ~p{two}, ~p{three}~n",[{one,1},{name,"Mike"},{three,3},{two,2}],1]).
{1,true}

Если есть лучший способ сравнительного анализа в эрланге, пожалуйста, дайте мне знать.



Код: (который был пересмотрен в соответствии с предложением Дуга)

-module(fout).

-export([format/2,benchmark_format_overhead/3]).

benchmark_format_overhead(_,_,0)->
    true;
benchmark_format_overhead(OString,OList,Loops) ->
    {FString,FNames}=parse_string(OString,ONames),
    benchmark_format_overhead(OString,OList,Loops-1).

format(OString,ONames) ->
    {FString,FNames}=parse_string(OString,ONames),
    io:format(FString,FNames).

parse_string(FormatString,Names) ->
    {F,N}=parse_format(FormatString),
    {F,substitute_names(N,Names)}.

parse_format(FS) ->
    parse_format(FS,"",[],"").

parse_format("",FormatString,ParamList,"")->
    {lists:reverse(FormatString),lists:reverse(ParamList)};
parse_format([${|FS],FormatString,ParamList,"")->
    parse_name(FS,FormatString,ParamList,"");
parse_format([$}|_FS],FormatString,_,_) ->
    throw({'unmatched } found',lists:reverse(FormatString)});
parse_format([C|FS],FormatString,ParamList,"") ->
    parse_format(FS,[C|FormatString],ParamList,"").

parse_name([$}|FS],FormatString,ParamList,ParamName) ->
    parse_format(FS,FormatString,[list_to_atom(lists:reverse(ParamName))|ParamList],"");
parse_name([${|_FS],FormatString,_,_) ->
    throw({'additional { found',lists:reverse(FormatString)});
parse_name([C|FS],FormatString,ParamList,ParamName) ->
    parse_name(FS,FormatString,ParamList,[C|ParamName]).

substitute_names(Positioned,Values) ->
    lists:map(fun(CN)->
                        case lists:keysearch(CN,1,Values) of
                            false ->
                                throw({'named parameter not found',CN,Values});
                            {_,{_,V}} ->
                                V
                        end end,
              Positioned).

Поскольку это было учебное упражнение, я надеялся, что те, кто более опытен в erlang, могут дать мне советы о том, как улучшить мой код.

Ура, Mike

Ответы [ 3 ]

2 голосов
/ 07 января 2009

В дополнение к предложению Дуга я бы не стал использовать здесь atom_to_list/1 - код заменяющих имен не нуждается в них, и генерация атомов во время выполнения - почти всегда плохая идея. Струны будут отлично работать.

parse_name([$}|FS],FormatString,ParamList,ParamName) ->
    parse_format(FS,FormatString,[lists:reverse(ParamName)|ParamList],"");
parse_name([${|_FS],FormatString,_,_) ->
    throw({'additional { found',lists:reverse(FormatString)});
parse_name([C|FS],FormatString,ParamList,ParamName) ->
    parse_name(FS,FormatString,ParamList,[C|ParamName]).

Я бы также использовал proplists: get_value вместо lists:keysearch/3 - когда у вас есть список кортежей из двух элементов {Name, Value}, как мы здесь делаем, использование кода proplists - путь, который все еще немного запутан поскольку нам нужен оператор case для проверки пропущенных значений, чтобы мы могли аварийно завершить работу с лучшей ошибкой.

substitute_names(Positioned,Values) ->
    [ case proplists:get_value(Name, Values) of
          undefined -> erlang:exit({missing_parameter, Name});
          V -> V
      end
      || Name <- Positioned ].

Поскольку это библиотека, она должна быть заменой io_lib, а не io. Таким образом, нам не нужно предоставлять все альтернативные предложения io (необязательный аргумент IoDevice и т. Д.).

format(OString,ONames) ->
    {FString,FNames}=parse_string(OString,ONames),
    io_lib:format(FString,FNames).

В общем, сплошной код. Если вы хотите лицензировать его под BSD или что-то подобное, я бы очень хотел добавить его в свой код веб-фреймворка Ejango .

2 голосов
/ 04 января 2009

Без комментариев об алгоритме или об использовании соответствующих библиотечных функций ...

Я бы ожидал увидеть более широкое использование сопоставления с образцом и рекурсии; например, parse_character (больше не свернутый) можно заменить на что-то вроде:

parse_in_format ([], FmtStr, ParmStrs, ParmName) -> {FmtStr, ParmStrs};
parse_in_format ([${ | Vr], FmtStr, ParmStrs, ParmName) -> parse_in_name (Vr, FmtStr, ParmStrs, ParmName);
parse_in_format ([$} | Vr], FmtStr, ParmStrs, ParmName) -> throw() % etc.
parse_in_format ([V | Vr], FmtStr, ParmStrs, ParmName) -> parse_in_format (Vr, [V | FmtStr], ParmStrs, ParmName).

parse_in_name ([], FmtStr, ParmStrs, ParmName) -> throw() % etc.
parse_in_name ([$} | Vr], FmtStr, ParmStrs, ParmName) -> parse_in_format (Vr, FmtStr, [list_to_atom(lists:reverse(ParmName))|ParmStrs], "");
parse_in_name ([${ | Vr], FmtStr, ParmStrs, ParmName) -> throw() % etc.
parse_in_name ([V | Vr], FmtStr, ParmStrs, ParmName) -> parse_in_name (Vr, FmtStr, ParmStrs, [V | ParmName]).

начато с

parse_in_format (FormatStr,  [], [], "");
1 голос
/ 04 января 2009

Если вы не знаете, влияют ли накладные расходы на цикл на ваш код, вам следует измерить его. Это просто.

-define(COLOOPS, 1000000).

-export([call_overhead/1,measure_call_overhead/0, measure_call_overhead/1]).

% returns overhead in us 
measure_call_overhead() -> measure_call_overhead(?COLOOPS).
measure_call_overhead(N) -> element(1, timer:tc(?MODULE, call_overhead, [N]))/N.

call_overhead(0)->ok;
call_overhead(N)->
    ok=nop(),
    call_overhead(N-1).

nop()->ok.

Это около 50 нс на моем ноутбуке. Я думаю, что это не должно сильно влиять на ваш текущий код.

Другой способ измерения - использование статистики (wall_clock) или статистики (время выполнения), которая возвращает время в мс Преимущество в том, что вам не нужна экспортная функция измерения. Это только косметическое улучшение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...