2009-01-04 3 views
6

Archaelus schlug in this post vor, dass das Schreiben einer neuen Format-Routine, um benannte Parameter zu behandeln, eine gute Lernübung sein kann. Also habe ich im Geiste des Erlernens der Sprache eine Formatierungsroutine geschrieben, die benannte Parameter behandelt.Drucken benannter Parameter



Ein Beispiel:

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 



Die 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 

Obwohl ich vermute, th Ein Großteil dieses Overheads ist auf Schleifen zurückzuführen, da ein Aufruf der Funktion mit einer Schleife eine Antwort in < 1us ergibt.

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} 

Wenn es einen besseren Weg zum Benchmarking in Erlang gibt, lass es mich wissen.



Der Code: Ich hatte gehofft, eine Lernübung,

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

Da dies (die mit Doug Vorschlag entsprechend überarbeitet wurde), dass diejenigen, erfahreneren mit erlang mir geben könnte Tipps, wie ich meinen Code verbessern kann.

Cheers, Mike

+0

Für Timing-Zwecke, wenn es zu schnell ist, um zu messen, sollten Sie es in einer Schleife ausführen und den Durchschnitt als die Zeit nehmen –

+0

Bitte umformulieren Sie Ihre Frage im Titel, ich würde nicht wissen, was Sie gefragt haben, wenn ich sah es in den Suchergebnissen. – Soviut

Antwort

2

Ohne Kommentar auf dem Algorithmus, oder bei der Verwendung von entsprechenden Bibliotheksfunktionen ...

ich erwartet hätte mehr Einsatz von Pattern-Matching und Rekursion zu sehen; zum Beispiel parse_character (nicht mehr gefaltet) könnte mit so etwas wie Fassung:

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

mit einem

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

Dank Doug, ich stimme zu, dass diese Methode überlegen ist, ich schreibe es im Moment mit weniger Bibliotheksfunktionen neu schreiben. Erlang ist meine erste funktionale Sprache, deshalb gehe ich immer noch in das funktionale Denken über. Danke für Ihren Kommentar! –

1

Kicked off Wenn Sie nicht wissen, ob Schleifenoverhead Code beeinflussen viel sollten Sie es messen . Es ist einfach.

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

Es ist etwa 50ns auf meinem Laptop. Ich denke, das sollte Ihren derzeitigen Code nicht so sehr beeinflussen.

Eine andere Methode zur Messung ist die direkte Verwendung von Statistiken (wall_clock) oder Statistiken (Laufzeit), die die Zeit in ms zurückgibt. Vorteil ist, dass Sie keine exportgemessene Funktion benötigen. Es ist nur kosmetische Verbesserung.

2

Zusätzlich zu Dougs Vorschlag würde ich vermeiden, atom_to_list/1 hier zu verwenden - der Ersatznamen-Code benötigt sie nicht und das Erzeugen von Atomen zur Laufzeit ist fast immer eine schlechte Idee. Saiten werden perfekt funktionieren.

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

ich auch Maker proplists verwenden würde: get_value statt lists:keysearch/3 - wenn Sie eine Liste von zwei Elementen Tupeln {Name, Value} haben, wie wir hier tun, ist die proplists Code mit dem Weg zu gehen - als wir noch ein wenig chaotisch Die case-Anweisung muss nach fehlenden Werten suchen, damit wir mit einem besseren Fehler abstürzen können.

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

Da dies eine Bibliothek ist, sollte es ein Ersatz für io_lib, nicht io. Auf diese Weise müssen wir nicht alle Alternativen zur Verfügung stellen io bietet (optional IoDevice Argument und so weiter).

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

Alles in allem solider Code. Wenn Sie bereit sind, es unter BSD oder ähnlichem zu lizenzieren, möchte ich es meinem Web-Framework-Code Ejango hinzufügen.

+0

Entschuldigung für die verspätete Antwort, ich war in den letzten 2 Wochen im Urlaub. Vielen Dank für Ihre ausführlichen Vorschläge und Erklärungen, sie waren sehr hilfreich. Ich bin glücklich, den Code unter BSD zu lizenzieren, also fühlen Sie sich frei, es zu verwenden, wie Sie wünschen (und mit irgendwelchen Änderungen, die Sie wünschen). Prost, Mike –