打印命名参数
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果没有对算法或适当库函数的使用进行评论......
我本希望看到更多地使用模式匹配和递归; 例如,parse_character(不再折叠)可能会被替换为以下内容
:
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:
Kicked off with a
除了 doug 的建议之外,我还避免在这里使用atom_to_list/1 - 替代名称代码不需要它们,并且在运行时生成原子几乎总是一个坏主意。 字符串将完美地工作。
我还会使用 proplists:get_value 而不是
lists:keysearch/3
- 当您有两个元素元组{Name, Value}
的列表时,就像我们在这里所做的那样,使用proplists
代码是可行的方法 - 它仍然有点混乱,因为我们需要 case 语句来检查缺失的值,这样我们就可以因更好的错误而崩溃。由于这是一个库,因此它应该替代
io_lib
,而不是io
。 这样我们就不必提供 io 提供的所有替代方案(可选的 IoDevice 参数等)。总而言之,可靠的代码。 如果您愿意在 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.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 theproplists
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.As this is a library, it should be a replacement for
io_lib
, notio
. This way we don't have to provide all the alternativesio
offers (optionalIoDevice
argument and so on).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.
如果您不知道循环开销是否对您的代码影响很大,您应该测量它。 这很简单。
在我的笔记本电脑上大约是 50ns。 我认为这不会对您当前的代码产生太大影响。
另一种测量方法是直接使用统计信息(wall_clock)或统计信息(运行时),它返回以毫秒为单位的时间。 好处是你不需要导出测量函数。 这只是化妆品的改进。
If you don't know if looping overhead affect your code much you should measure it. It's simple.
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.