Skip to content

Commit 5b5c36f

Browse files
committed
Merge branch 'master' of github.com:rssh/notes
2 parents b067b71 + 148a8d7 commit 5b5c36f

File tree

1 file changed

+12
-11
lines changed

1 file changed

+12
-11
lines changed

2024_12_09_dependency-injection.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ Find time to modernize dependency injection in some services. The previous versi
2222
However, for relatively small applications, it is possible to live with.
2323

2424

25-
If we switch to type-driven context resolving (i.e., use AppContext[Service1] instead of appContext.service1 ), we will solve the modularization problem.
25+
If we switch to type-driven context resolving (i.e., use `AppContext[Service1]` instead of `appContext.service1` ), we will solve the modularization problem.
2626

2727
The first look was at the approach described by @odersky in https://old.reddit.com/r/scala/comments/1eksdo2/automatic_dependency_injection_in_pure_scala/.
2828
(code: https://github.com/scala/scala3/blob/main/tests/run/Providers.scala )
2929

30-
Unfortunately, the proposed technique is not directly applicable to our cases. The main reason is that machinery with math types works only when all types in the tuple are distinct. Therefore, this means all components participating in dependency injection should not be traits. (because if we have two traits (A, B), we can't prove that A and B in general are distinct; therefore, Index[(A, B)] will not resolved. The limitation not to use traits as components is too strict for practical usage. Two other problems (absence of chaining of providers and absence of caching) are fixable and demonstrate a usual gap between 'proof of concept’ and real stuff.
30+
Unfortunately, the proposed technique is not directly applicable to our cases. The main reason is that machinery with matсh types works only when all types in the tuple are distinct. Therefore, this means all components participating in dependency injection should not be traits. (because if we have two traits (A, B), we can't prove that A and B in general are distinct; therefore, Index[(A, B)] will not resolved. The limitation not to use traits as components is too strict for practical usage. Two other problems (absence of chaining of providers and absence of caching) are fixable and demonstrate a usual gap between 'proof of concept’ and real stuff.
3131

3232
We will use approaches very close to what sideEffffECt describe in https://www.reddit.com/r/scala/comments/1eqqng2/the_simplest_dependency_injection_pure_scala_no/ with few additional steps.
3333

3434

3535
# Basic
3636

37-
We will think that component `C` is provided if we can find AppProvicer[C]]
37+
We will think that component `C` is provided if we can find AppProvider[C]]
3838

3939
```Scala
4040
trait AppContextProvider[T] {
@@ -58,8 +58,9 @@ If we have an implicit instance of the object, we think it's provided:
5858

5959

6060
```Scala
61-
object AppContextProvider extends AppContextProviderLowLevel {
61+
object AppContextProvider {
6262

63+
....
6364

6465
given ofGivem[T](using T): AppContextProvider[T] with {
6566
def get: T = summon[T]
@@ -178,9 +179,9 @@ object AppContextProviders {
178179
```
179180

180181

181-
(complete code is available in the repository: https://github.com/rssh/scala-appcontext )
182+
(complete code is available in the repository: https://github.com/rssh/scala-appcontext ; permalink to generateImpl: https://github.com/rssh/scala-appcontext/blob/666a02e788aa57922104569541511a16431690fb/shared/src/main/scala/com/github/rssh/appcontext/AppContextProviders.scala#L52 )
182183

183-
We separate `AppContextProvidersSearch` and `AppContextProviders` because we don't want to trigger AppContextProviders' implicit generation during implicit search outside of service instance generation.
184+
We separate `AppContextProvidersSearch` and `AppContextProviders` because we don't want to trigger `AppContextProviders` implicit generation during implicit search outside of service instance generation.
184185
Note that Scala currently has no way to make a macro that generates a given instance to fail an implicit search silently. We can only make errors during the search, which will abandon the whole compilation.
185186

186187
Can we also remove the boilerplate when defining the implicit AppContext provider?
@@ -208,7 +209,7 @@ object UserSubscription {
208209

209210
But this will still be boilerplate: We must enumerate dependencies twice and write trivial instance creation. On the other hand, this instance creation is not entirely meaningless: we can imagine the situation when it's not automatic.
210211

211-
To minimize this kind of boilerplate, we can introduce a convention for AppContextProviderModule, which defines its dependencies in type and automatic generation of instance providers:
212+
To minimize this kind of boilerplate, we can introduce a convention for `AppContextProviderModule`, which defines its dependencies in type and automatic generation of instance providers:
212213

213214
```Scala
214215
trait AppContextProviderModule[T] {
@@ -285,7 +286,7 @@ object AppContext {
285286
```
286287

287288

288-
And let's deploy a simple convention: if the service requires `AppContext.Cache` as a dependency, then we consider this service cached. I.e., with manual setup of AppContextProvider this should look like this:
289+
And let's deploy a simple convention: if the service requires `AppContext.Cache` as a dependency, then we consider this service cached. I.e., with manual setup of `AppContextProvider` this should look like this:
289290

290291
```Scala
291292
object FuelUsage {
@@ -316,7 +317,7 @@ class TestUserSubscription(using TestUserSubscription.DependenciesProviders)
316317
...
317318
```
318319

319-
#Preventing pitfalls
320+
# Preventing pitfalls
320321

321322
Can this be considered a complete mini-framework? Still waiting.
322323
Let’s look at the following code:
@@ -346,9 +347,9 @@ println(c3.doSomething())
346347

347348
What will be printed?
348349

349-
The correct answer is “dep1:module:dep2:local”, because resolving of Dependency1 from the companion object of Dependency1 will be preferred over resolving from the AppContextProvider companion object. Unfortunately, I don’t know how to change this.
350+
The correct answer is `“dep1:module:dep2:local”`, because resolving of `Dependency1` from the companion object will be preferred over resolving from the `AppContextProvider` companion object. Unfortunately, I don’t know how to change this.
350351

351-
We can add a check to determine whether supplied providers are needed. Again, unfortunately, we can’t add it ‘behind the scenes' by modifying the generator of AppContextProvider because the generator is inlined in the caller context for the component instance, where all dependencies should be resolved.
352+
We can add a check to determine whether supplied providers are needed. Again, unfortunately, we can’t add it ‘behind the scenes' by modifying the generator of `AppContextProvider` because the generator is inlined in the caller context for the component instance, where all dependencies should be resolved.
352353
We can write a macro that should be called from the context inside a component definition. This will require the developer to call it explicitly.
353354

354355
I.e., a typical component definition will look like this:

0 commit comments

Comments
 (0)