You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In this part of the documentation, we will set aside typing considerations and focus on the strategy used to search for a trait at runtime within a provider construct.
3
+
In this part of the documentation, we will set aside typing considerations
4
+
and focus on the strategy used to search for a trait at runtime within a
5
+
provider construct.
4
6
5
-
We will describe the currently implemented strategy and discuss possible trade-offs.
7
+
We will describe the currently implemented strategy and discuss possible
8
+
trade-offs.
6
9
7
10
## Runtime representation
8
11
9
-
Under the hood, a provider is an array where each cell is a binding. This binding is a pair that associates a trait with its implementation.
12
+
Under the hood, a provider is an array where each cell is a binding. This
13
+
binding is a pair that associates a trait with its implementation.
10
14
11
-
To simplify, we will consider traits as integers and implementations as strings. This makes the examples easier to read.
15
+
To simplify, we will consider traits as integers and implementations as
16
+
strings. This makes the examples easier to read.
12
17
13
18
```ocaml
14
19
type impl = string
15
-
16
20
type trait = int
17
-
18
21
type provider = (trait * impl) array
19
22
```
20
23
21
-
We are interested in defining lookup strategies — functions that return the implementation bound to a trait if the provider implements it.
24
+
We are interested in defining lookup strategies — functions that return the
25
+
implementation bound to a trait if the provider implements it.
22
26
23
27
```ocaml
24
28
type lookup_strategy = provider -> trait -> impl option
test linear_scan [| 134, "Hello"; 7, "World" |] 13;
52
+
[%expect {| None |}];
53
+
test linear_scan [| 134, "Hello"; 7, "World" |] 134;
54
+
[%expect {| Some "Hello" |}];
55
+
()
56
+
;;
48
57
```
49
58
50
-
This strategy is simple and does not require any specific ordering within the bindings. It runs in `O(n)`, where `n` is the number of traits. When `n` is small, this is likely to be very fast, if not the fastest implementation.
59
+
This strategy is simple and does not require any specific ordering within the
60
+
bindings. It runs in `O(n)`, where `n` is the number of traits. When `n` is
61
+
small, this is likely to be very fast, if not the fastest implementation.
51
62
52
63
### Binary Search
53
64
54
65
This is a slight twist on the linear scan.
55
66
56
67
```ocaml
57
-
let binary_search : lookup_strategy = fun provider trait ->
68
+
let binary_search : lookup_strategy =
69
+
fun provider trait ->
58
70
Binary_search.binary_search
59
71
provider
60
72
~length:Array.length
@@ -66,49 +78,65 @@ let binary_search : lookup_strategy = fun provider trait ->
66
78
;;
67
79
```
68
80
69
-
In this strategy, the bindings need to be ordered in increasing order according to their traits. Typically, a provider is constructed once and then passed around and used many times during its lifetime. This approach lends itself well to the idea of performing a bit more computation upfront to achieve a systematic speedup during subsequent use.
81
+
In this strategy, the bindings need to be ordered in increasing order
82
+
according to their traits. Typically, a provider is constructed once and then
83
+
passed around and used many times during its lifetime. This approach lends
84
+
itself well to the idea of performing a bit more computation upfront to
85
+
achieve a systematic speedup during subsequent use.
If we are frequently searching for the same trait multiple times in a row, we might benefit from using a cache.
115
+
If we are frequently searching for the same trait multiple times in a row, we
116
+
might benefit from using a cache.
98
117
99
-
When we started implementing the library, we were unsure whether a cache would be useful. We decided to implement a simple caching mechanism as a proof of concept. This approach allowed us to evaluate whether we needed to change the representation early on, avoiding potential incompatible changes in later versions.
118
+
When we started implementing the library, we were unsure whether a cache
119
+
would be useful. We decided to implement a simple caching mechanism as a
120
+
proof of concept. This approach allowed us to evaluate whether we needed to
121
+
change the representation early on, avoiding potential incompatible changes
122
+
in later versions.
100
123
101
-
The strategy we implemented uses the first cell (index 0) of the array to store the most recently accessed trait. The rest of the array (from index 1 to n) is kept sorted by trait and is searched using a binary search.
124
+
The strategy we implemented uses the first cell (index 0) of the array to
125
+
store the most recently accessed trait. The rest of the array (from index 1
126
+
to n) is kept sorted by trait and is searched using a binary search.
102
127
103
128
```ocaml
104
-
let binary_search_with_cache : lookup_strategy = fun provider trait ->
129
+
let binary_search_with_cache : lookup_strategy =
130
+
fun provider trait ->
105
131
if Array.length provider = 0
106
-
then None
107
-
else
132
+
then None [@coverage off]
133
+
else (
108
134
let cache = provider.(0) in
109
135
if Int.equal (fst cache) trait
110
-
then (print_endline "Hitting the cache!"; Some (snd cache))
111
-
else
136
+
then (
137
+
Stdlib.print_endline "Hitting the cache!";
138
+
Some (snd cache))
139
+
else (
112
140
match
113
141
Binary_search.binary_search
114
142
provider
@@ -122,82 +150,99 @@ let binary_search_with_cache : lookup_strategy = fun provider trait ->
We are not entirely convinced that the cache is useful. It is difficult to determine its utility without more information about how the library is actually used. Additionally, we envision that:
204
+
We are not entirely convinced that the cache is useful. It is difficult to
205
+
determine its utility without more information about how the library is
206
+
actually used. Additionally, we envision that:
171
207
172
-
- Defaulting to a linear scan may be faster overall in cases where the number of traits is small.
173
-
- The cache introduces complexity if the provider is accessed by multiple domains in parallel. In such cases, you may prefer to disable it entirely.
208
+
- Defaulting to a linear scan may be faster overall in cases where the number
209
+
of traits is small.
210
+
- The cache introduces complexity if the provider is accessed by multiple
211
+
domains in parallel. In such cases, you may prefer to disable it entirely.
174
212
175
-
Therefore, our future plans may include tweaking the lookup strategy, conducting benchmarks, and providing more control to the user.
213
+
Therefore, our future plans may include tweaking the lookup strategy,
214
+
conducting benchmarks, and providing more control to the user.
176
215
177
216
We are not concerned about breaking changes because:
178
217
179
-
1. Building a provider is done through a function call to which we could add optional parameters, such as `~enable_cache:bool`.
218
+
1. Building a provider is done through a function call to which we could add
219
+
optional parameters, such as `~enable_cache:bool`.
180
220
181
-
2. There exists a simple criterion to determine whether a provider has a cache location at position 0:
221
+
2. There exists a simple criterion to determine whether a provider has a
This demonstrates how we can recognize whether caching was enabled at provider creation time and act accordingly.
239
+
This demonstrates how we can recognize whether caching was enabled at
240
+
provider creation time and act accordingly.
200
241
201
242
## Conclusion
202
243
203
-
In this documentation, we've explained the runtime representation of a provider and discussed different lookup strategies for searching traits. We've documented the current strategy, which uses a binary search and a simple cache, and concluded by offering some ideas for future improvements while maintaining good compatibility with the existing code.
244
+
In this documentation, we've explained the runtime representation of a
245
+
provider and discussed different lookup strategies for searching traits.
246
+
We've documented the current strategy, which uses a binary search and a
247
+
simple cache, and concluded by offering some ideas for future improvements
248
+
while maintaining good compatibility with the existing code.
0 commit comments