Question

Archaelus a suggéré de cet article , selon lequel écrire une nouvelle routine de format pour gérer les paramètres nommés peut être un bon exercice d'apprentissage. Donc, dans l’esprit d’apprentissage de la langue, j’ai écrit une routine de formatage qui gère les paramètres nommés.



Un exemple:

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



L'indice de référence:

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

Bien que je soupçonne qu’une bonne partie de cette surcharge est due à la mise en boucle, puisqu’appeler une fonction avec une boucle produit une réponse 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}

S'il existe une meilleure méthode de comparaison en erlang, merci de me le faire savoir.



Code: (qui a été révisé conformément à la suggestion 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).

Comme il s’agissait d’un exercice d’apprentissage, j’espérais que les personnes plus expérimentées en erlang pourraient me donner des conseils pour améliorer mon code.

Salut, Mike

Était-ce utile?

La solution

Sans commentaire sur l'algorithme, ni sur l'utilisation des fonctions de bibliothèque appropriées ...

Je me serais attendu à voir plus d’utilisation de la correspondance de modèle et de la récursivité; Par exemple, parse_character (qui n'est plus plié) peut être remplacé par quelque chose comme:

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

Lancé avec un

parse_in_format (FormatStr,  [], [], "");

Autres conseils

En plus de la suggestion de Doug, j'éviterais d'utiliser atom_to_list/1 ici - le code des noms de substitution n'en a pas besoin et la génération d'atomes à l'exécution est presque toujours une mauvaise idée. Les cordes fonctionneront parfaitement.

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

Je voudrais aussi utiliser proplists: get_value au lieu de lists:keysearch/3 - quand vous avez une liste de deux tuples d'éléments {Name, Value} comme nous le faisons ici, utiliser le code proplists est le chemin à parcourir - c'est toujours un peu compliqué car nous avons besoin de l'instruction case pour vérifier les valeurs manquantes afin de nous planter avec une meilleure erreur.

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

S'agissant d'une bibliothèque, il convient de remplacer io_lib et non io. De cette manière, nous n’avons pas à fournir toutes les alternatives IoDevice offres (argument <=> facultatif, etc.).

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

Tout compte fait, code solide. Si vous souhaitez utiliser la licence sous BSD ou quelque chose de similaire, j'aimerais tout simplement l'ajouter à mon code d'infrastructure Web Ejango .

Si vous ne savez pas si la surcharge de boucle affecte votre code, vous devez le mesurer. C'est 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.

Il est environ 50ns sur mon ordinateur portable. Je pense que cela ne devrait pas affecter autant votre code actuel.

Une autre façon de mesurer consiste à utiliser directement des statistiques (wall_clock) ou des statistiques (runtime) qui renvoient le temps en ms. L'avantage est que vous n'avez pas besoin d'exporter la fonction mesurée. Ce n’est que l’amélioration des cosmétiques.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top