From cd88825861250533858d6aff56a87a5ab9cecda1 Mon Sep 17 00:00:00 2001 From: Ariel Date: Mon, 18 Dec 2023 20:59:57 +0100 Subject: [PATCH] Syntax highlighted in code snippets --- doc/signatures.md | 286 +++++++++++++++++++++++----------------------- 1 file changed, 143 insertions(+), 143 deletions(-) diff --git a/doc/signatures.md b/doc/signatures.md index 3142c37..3e32c11 100644 --- a/doc/signatures.md +++ b/doc/signatures.md @@ -39,20 +39,20 @@ directly. There are a few ways you can approximate it. One way is to pass the Module name to the calling functions along with the data that it is going to be called on. - ```erlang - add(ModuleToUse, Key, Value, DictData) -> - ModuleToUse:add(Key, Value, DictData). - ``` +```erlang +add(ModuleToUse, Key, Value, DictData) -> + ModuleToUse:add(Key, Value, DictData). +``` This works, and you can vary how you want to pass the data. For example, you could easily use a tuple to contain the data. That is, you could pass in `{ModuleToUse, DictData}` and that would make it a bit cleaner. - ```erlang - add(Key, Value, {ModuleToUse, DictData}) -> - ModuleToUse:add(Key, Value, DictData). - ``` +```erlang +add(Key, Value, {ModuleToUse, DictData}) -> + ModuleToUse:add(Key, Value, DictData). +``` Either way, there are a few problems with this approach. One of the biggest is that you lose code locality, by looking at this bit of code @@ -78,10 +78,10 @@ name. So what we actually want to do is something mole like this: - ```erlang - add(Key, Value, DictData) -> - dictionary:add(Key, Value, DictData). - ``` +```erlang +add(Key, Value, DictData) -> + dictionary:add(Key, Value, DictData). +``` Doing this we retain the locality. We can easily look up the `dictionary` Module. We immediately have a good idea what a @@ -101,25 +101,25 @@ a [Behaviour](http://metajack.im/2008/10/29/custom-behaviors-in-erlang/) for our functionality. To continue our example we will define a Behaviour for dictionaries. That Behaviour looks like this: - ```erlang - -module(ec_dictionary). +```erlang +-module(ec_dictionary). - -export([behaviour_info/1]). +-export([behaviour_info/1]). - behaviour_info(callbacks) -> - [{new, 0}, - {has_key, 2}, - {get, 2}, - {add, 3}, - {remove, 2}, - {has_value, 2}, - {size, 1}, - {to_list, 1}, - {from_list, 1}, - {keys, 1}]; - behaviour_info(_) -> - undefined. - ``` +behaviour_info(callbacks) -> + [{new, 0}, + {has_key, 2}, + {get, 2}, + {add, 3}, + {remove, 2}, + {has_value, 2}, + {size, 1}, + {to_list, 1}, + {from_list, 1}, + {keys, 1}]; +behaviour_info(_) -> + undefined. +``` So we have our Behaviour now. Unfortunately, this doesn't give us much @@ -129,15 +129,15 @@ dictionaries in an abstract way in our code. To do that we need to add a bit of functionality. We do that by actually implementing our own behaviour, starting with `new/1`. - ```erlang - %% @doc create a new dictionary object from the specified module. The - %% module should implement the dictionary behaviour. - %% - %% @param ModuleName The module name. - -spec new(module()) -> dictionary(_K, _V). - new(ModuleName) when is_atom(ModuleName) -> - #dict_t{callback = ModuleName, data = ModuleName:new()}. - ``` +```erlang +%% @doc create a new dictionary object from the specified module. The +%% module should implement the dictionary behaviour. +%% +%% @param ModuleName The module name. +-spec new(module()) -> dictionary(_K, _V). +new(ModuleName) when is_atom(ModuleName) -> + #dict_t{callback = ModuleName, data = ModuleName:new()}. +``` This code creates a new dictionary for us. Or to be more specific it actually creates a new dictionary Signature record, that will be used @@ -154,17 +154,17 @@ dictionary and another that just retrieves data. The first we will look at is the one that updates the dictionary by adding a value. - ```erlang - %% @doc add a new value to the existing dictionary. Return a new - %% dictionary containing the value. - %% - %% @param Dict the dictionary object to add too - %% @param Key the key to add - %% @param Value the value to add - -spec add(key(K), value(V), dictionary(K, V)) -> dictionary(K, V). - add(Key, Value, #dict_t{callback = Mod, data = Data} = Dict) -> - Dict#dict_t{data = Mod:add(Key, Value, Data)}. - ``` +```erlang +%% @doc add a new value to the existing dictionary. Return a new +%% dictionary containing the value. +%% +%% @param Dict the dictionary object to add too +%% @param Key the key to add +%% @param Value the value to add +-spec add(key(K), value(V), dictionary(K, V)) -> dictionary(K, V). +add(Key, Value, #dict_t{callback = Mod, data = Data} = Dict) -> + Dict#dict_t{data = Mod:add(Key, Value, Data)}. +``` There are two key things here. @@ -180,17 +180,17 @@ implementation to do the work itself. Now lets do a data retrieval function. In this case, the `get` function of the dictionary Signature. - ```erlang - %% @doc given a key return that key from the dictionary. If the key is - %% not found throw a 'not_found' exception. - %% - %% @param Dict The dictionary object to return the value from - %% @param Key The key requested - %% @throws not_found when the key does not exist - -spec get(key(K), dictionary(K, V)) -> value(V). - get(Key, #dict_t{callback = Mod, data = Data}) -> - Mod:get(Key, Data). - ``` +```erlang +%% @doc given a key return that key from the dictionary. If the key is +%% not found throw a 'not_found' exception. +%% +%% @param Dict The dictionary object to return the value from +%% @param Key The key requested +%% @throws not_found when the key does not exist +-spec get(key(K), dictionary(K, V)) -> value(V). +get(Key, #dict_t{callback = Mod, data = Data}) -> + Mod:get(Key, Data). +``` In this case, you can see a very similar approach to deconstructing the dict record. We still need to pull out the callback module and the @@ -244,17 +244,17 @@ We will take a look at one of the functions we have already seen. The semantics as any of the functions in the `dict` module. So a bit of translation needs to be done. We do that in the `ec_dict:get/2` function. - ```erlang - -spec get(ec_dictionary:key(K), Object::dictionary(K, V)) -> - ec_dictionary:value(V). - get(Key, Data) -> - case dict:find(Key, Data) of - {ok, Value} -> - Value; +```erlang +-spec get(ec_dictionary:key(K), Object::dictionary(K, V)) -> + ec_dictionary:value(V). +get(Key, Data) -> + case dict:find(Key, Data) of + {ok, Value} -> + Value; error -> - throw(not_found) - end. - ``` + throw(not_found) + end. +``` So the `ec_dict` module's purpose for existence is to help the preexisting `dict` module implement the Behaviour defined by the @@ -276,13 +276,13 @@ create a couple of functions that create dictionaries for each type we want to test. The first we want to time is the Signature Wrapper, so `dict` vs `ec_dict` called as a Signature. - ```erlang - create_dict() -> - lists:foldl(fun(El, Dict) -> - dict:store(El, El, Dict) +```erlang +create_dict() -> + lists:foldl(fun(El, Dict) -> + dict:store(El, El, Dict) end, dict:new(), lists:seq(1,100)). - ``` +``` The only thing we do here is create a sequence of numbers 1 to 100, and then add each of those to the `dict` as an entry. We aren't too @@ -293,14 +293,14 @@ of the dictionaries themselves. We need to create a similar function for our Signature based dictionary `ec_dict`. - ```erlang - create_dictionary(Type) -> - lists:foldl(fun(El, Dict) -> - ec_dictionary:add(El, El, Dict) +```erlang +create_dictionary(Type) -> + lists:foldl(fun(El, Dict) -> + ec_dictionary:add(El, El, Dict) end, ec_dictionary:new(Type), lists:seq(1,100)). - ``` +``` Here we actually create everything using the Signature. So we don't need one function for each type. We can have one function that can @@ -313,17 +313,17 @@ data and one that returns data, just to get good coverage. For our dictionaries we are going to use the `size` function as well as the `add` function. - ```erlang - time_direct_vs_signature_dict() -> - io:format("Timing dict~n"), - Dict = create_dict(), - test_avg(fun() -> +```erlang +time_direct_vs_signature_dict() -> + io:format("Timing dict~n"), + Dict = create_dict(), + test_avg(fun() -> dict:size(dict:store(some_key, some_value, Dict)) end, 1000000), - io:format("Timing ec_dict implementation of ec_dictionary~n"), - time_dict_type(ec_dict). - ``` + io:format("Timing ec_dict implementation of ec_dictionary~n"), + time_dict_type(ec_dict). +``` The `test_avg` function runs the provided function the number of times specified in the second argument and collects timing information. We @@ -335,15 +335,15 @@ we don't have to hard code the calls for the Signature implementations. Lets take a look at the `time_dict_type` function. - ```erlang - time_dict_type(Type) -> - io:format("Testing ~p~n", [Type]), - Dict = create_dictionary(Type), - test_avg(fun() -> - ec_dictionary:size(ec_dictionary:add(some_key, some_value, Dict)) - end, - 1000000). - ``` +```erlang +time_dict_type(Type) -> + io:format("Testing ~p~n", [Type]), + Dict = create_dictionary(Type), + test_avg(fun() -> + ec_dictionary:size(ec_dictionary:add(some_key, some_value, Dict)) + end, + 1000000). +``` As you can see we take the type as an argument (we need it for `dict` creation) and call our create function. Then we run the same timings @@ -356,23 +356,23 @@ work for anything that implements that Signature. So we have our tests, what was the result. Well on my laptop this is what it looked like. - ```sh - Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false] +```sh +Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false] - Eshell V5.8.2 (abort with ^G) +Eshell V5.8.2 (abort with ^G) - 1> ec_timing:time_direct_vs_signature_dict(). - Timing dict - Range: 2 - 5621 mics - Median: 3 mics - Average: 3 mics - Timing ec_dict implementation of ec_dictionary - Testing ec_dict - Range: 3 - 6097 mics - Median: 3 mics - Average: 4 mics - 2> - ``` +1> ec_timing:time_direct_vs_signature_dict(). +Timing dict +Range: 2 - 5621 mics +Median: 3 mics +Average: 3 mics +Timing ec_dict implementation of ec_dictionary +Testing ec_dict +Range: 3 - 6097 mics +Median: 3 mics +Average: 4 mics +2> +``` So for the direct `dict` call, we average about 3 mics per call, while for the Signature Wrapper we average around 4. That's a 25% cost for @@ -387,13 +387,13 @@ Signature, but it is not a Signature Wrapper. It is a native implementation of the Signature. To use `ec_rbdict` directly we have to create a creation helper just like we did for dict. - ```erlang - create_rbdict() -> - lists:foldl(fun(El, Dict) -> - ec_rbdict:add(El, El, Dict) +```erlang +create_rbdict() -> + lists:foldl(fun(El, Dict) -> + ec_rbdict:add(El, El, Dict) end, ec_rbdict:new(), lists:seq(1,100)). - ``` +``` This is exactly the same as `create_dict` with the exception that dict is replaced by `ec_rbdict`. @@ -402,17 +402,17 @@ The timing function itself looks very similar as well. Again notice that we have to hard code the concrete name for the concrete implementation, but we don't for the `ec_dictionary` test. - ```erlang - time_direct_vs_signature_rbdict() -> - io:format("Timing rbdict~n"), - Dict = create_rbdict(), - test_avg(fun() -> - ec_rbdict:size(ec_rbdict:add(some_key, some_value, Dict)) - end, - 1000000), - io:format("Timing ec_dict implementation of ec_dictionary~n"), - time_dict_type(ec_rbdict). - ``` +```erlang +time_direct_vs_signature_rbdict() -> + io:format("Timing rbdict~n"), + Dict = create_rbdict(), + test_avg(fun() -> + ec_rbdict:size(ec_rbdict:add(some_key, some_value, Dict)) + end, + 1000000), + io:format("Timing ec_dict implementation of ec_dictionary~n"), + time_dict_type(ec_rbdict). +``` And there we have our test. What do the results look like? @@ -422,23 +422,23 @@ The main thing we are timing here is the additional cost of the dictionary Signature itself. Keep that in mind as we look at the results. - ```sh - Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false] +```sh +Erlang R14B01 (erts-5.8.2) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false] - Eshell V5.8.2 (abort with ^G) +Eshell V5.8.2 (abort with ^G) - 1> ec_timing:time_direct_vs_signature_rbdict(). - Timing rbdict - Range: 6 - 15070 mics - Median: 7 mics - Average: 7 mics - Timing ec_dict implementation of ec_dictionary - Testing ec_rbdict - Range: 6 - 6013 mics - Median: 7 mics - Average: 7 mics - 2> - ``` +1> ec_timing:time_direct_vs_signature_rbdict(). +Timing rbdict +Range: 6 - 15070 mics +Median: 7 mics +Average: 7 mics +Timing ec_dict implementation of ec_dictionary +Testing ec_rbdict +Range: 6 - 6013 mics +Median: 7 mics +Average: 7 mics +2> +``` So no difference it time. Well the reality is that there is a difference in timing, there must be, but we don't have enough