programacion - erlang tutorial
Imprimir parĂ¡metros nombrados (3)
Si no sabe si la sobrecarga de bucles afecta mucho a su código, debe medirlo. Es sencillo.
-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.
Son aproximadamente 50ns en mi computadora portátil. Creo que esto no debería afectar tanto tu código actual.
Otra forma de medir es usar directamente estadísticas (wall_clock) o estadísticas (runtime) que devuelve el tiempo en ms. El beneficio es que no necesita exportar la función medida. Es solo una mejora en los cosméticos.
Archaelus sugirió en este post que escribir una nueva rutina de formato para manejar los parámetros nombrados puede ser un buen ejercicio de aprendizaje. Entonces, en el espíritu de aprender el idioma, escribí una rutina de formateo que maneja los parámetros nombrados.
Un ejemplo:
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
El punto de referencia:
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
Aunque sospecho que gran parte de esta sobrecarga se debe a un bucle, ya que llamar a la función con un bucle produce una respuesta en <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}
Si hay una forma mejor de evaluación comparativa en erlang, por favor avíseme.
El Código: (que ha sido revisado de acuerdo con la sugerencia de 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).
Como se trataba de un ejercicio de aprendizaje, esperaba que aquellos con más experiencia con Erlang pudieran darme consejos sobre cómo mejorar mi código.
Saludos, Mike
Sin comentarios sobre el algoritmo, o sobre el uso de las funciones apropiadas de la biblioteca ...
Hubiera esperado ver un mayor uso de coincidencia de patrones y recursividad; por ejemplo, parse_character (ya no doblado) podría reemplazarse por algo como:
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]).
Arrancado con un
parse_in_format (FormatStr, [], [], "");
Además de la sugerencia de doug, evitaría usar atom_to_list/1
aquí - el código de nombres sustituto no los necesita y la generación de átomos en el tiempo de ejecución es casi siempre una mala idea. Las cuerdas funcionarán perfectamente bien.
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]).
También usaría las listas: get_value en lugar de lists:keysearch/3
- cuando tienes una lista de dos elementos tuplas {Name, Value}
como lo hacemos aquí, usar el código de proplists
es el camino a seguir, sigue siendo un poco complicado Necesitamos el enunciado de caso para verificar valores perdidos para que podamos colgar con un mejor error.
substitute_names(Positioned,Values) ->
[ case proplists:get_value(Name, Values) of
undefined -> erlang:exit({missing_parameter, Name});
V -> V
end
|| Name <- Positioned ].
Como esta es una biblioteca, debería ser un reemplazo para io_lib
, no io
. De esta forma, no tenemos que proporcionar todas las alternativas io
(argumento de IoDevice
opcional, etc.).
format(OString,ONames) ->
{FString,FNames}=parse_string(OString,ONames),
io_lib:format(FString,FNames).
En general, código sólido. Si está dispuesto a licenciarlo bajo BSD o algo similar, me gustaría agregarlo al código de mi framework web Ejango .