Syntax highlighted in code snippets

This commit is contained in:
Ariel 2023-12-18 20:59:57 +01:00
parent 5c5c264241
commit cd88825861

View file

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