打印命名参数

发布于 2024-07-10 08:54:38 字数 2869 浏览 6 评论 0原文

Archaelus 在这篇文章中建议< /a> 编写一个新的格式例程来处理命名参数可能是一个很好的学习练习。 因此,本着学习该语言的精神,我编写了一个处理命名参数的格式化例程。



示例:

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。

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}

如果 erlang 有更好的基准测试方法,请告诉我。



代码: (已根据Doug的建议进行了修改)

-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更有经验的人可以给我关于如何改进我的代码的提示。

干杯, 麦克风

Archaelus suggested in this post that writing a new format routine to handle named parameters may be a good learning exercise. So, in the spirit of learning the language I wrote a formatting routine which handles named parameters.

An Example:

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

The Benchmark:

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

Although I suspect that much of this overhead is due to looping, as a calling the function with one loop yields a response in < 1us.

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}

If there is a better way of benchmarking in erlang, please let me know.

The Code:
(which has been revised in accordance with Doug's suggestion)

-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).

As this was a learning exercise, I was hoping that those more experienced with erlang could give me tips on how to improve my code.

Cheers,
Mike

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

南街九尾狐 2024-07-17 08:54:38

如果没有对算法或适当库函数的使用进行评论......

我本希望看到更多地使用模式匹配和递归; 例如,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,  [], [], "");

Without comment on the algorithm, or on use of appropriate library functions...

I would have expected to see more use of pattern matching and recursion; for example parse_character (no longer folded) might be replaced with something like:

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]).

Kicked off with a

parse_in_format (FormatStr,  [], [], "");
霓裳挽歌倾城醉 2024-07-17 08:54:38

除了 doug 的建议之外,我还避免在这里使用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 或类似的东西下授权它,我很想将它添加到我的 Web 框架代码 Ejango

In addition to doug's suggestion, I'd avoid using atom_to_list/1 here - the substitute names code doesn't need them and generating atoms at runtime is almost always a bad idea. Strings will work perfectly well.

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]).

I would also use proplists:get_value instead of lists:keysearch/3 - when you have a list of two element tuples {Name, Value} as we do here, using the proplists code is the way to go - it's still a little messy as we need the case statement to check for missing values so we can crash with a better error.

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

As this is a library, it should be a replacement for io_lib, not io. This way we don't have to provide all the alternatives io offers (optional IoDevice argument and so on).

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

All in all, solid code. If you're willing to license it under BSD or something similar, I'd quite like to add it to my web framework code Ejango.

执着的年纪 2024-07-17 08:54:38

如果您不知道循环开销是否对您的代码影响很大,您应该测量它。 这很简单。

-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.

在我的笔记本电脑上大约是 50ns。 我认为这不会对您当前的代码产生太大影响。

另一种测量方法是直接使用统计信息(wall_clock)或统计信息(运行时),它返回以毫秒为单位的时间。 好处是你不需要导出测量函数。 这只是化妆品的改进。

If you don't know if looping overhead affect your code much you should measure it. It's simple.

-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.

It is about 50ns on my laptop. I think this should not affect your current code so much.

Another way how to measure is using directly statistics(wall_clock) or statistics(runtime) which returns time in ms. Benefit is that you don't need export measured function. It is only cosmetics improvement.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文