Skip to content

Commit 9b8d317

Browse files
committed
Add blog post: Solving the Implicit Search Priority Problem in AppContext
1 parent 250b72d commit 9b8d317

File tree

3 files changed

+111
-1
lines changed

3 files changed

+111
-1
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
title: Solving the Implicit Search Priority Problem in AppContext
3+
---
4+
5+
# Solving the Implicit Search Priority Problem
6+
7+
In the [first article about AppContext](https://github.com/rssh/notes/blob/master/2024_12_09_dependency-injection.md), we described a pitfall with implicit search order:
8+
9+
```scala
10+
case class Dependency1(name: String)
11+
12+
object Dependency1 {
13+
given AppContextProvider[Dependency1] = AppContextProvider.of(Dependency1("dep1:module"))
14+
}
15+
16+
class Component(using AppContextProviders[(Dependency1, Dependency2)]) {
17+
def doSomething(): String = {
18+
s"${AppContext[Dependency1].name}, ${AppContext[Dependency2].name}"
19+
}
20+
}
21+
22+
val dep1 = Dependency1("dep1:local")
23+
val dep2 = Dependency2("dep2:local")
24+
val c = Component(using AppContextProviders.of(dep1, dep2))
25+
println(c.doSomething()) // Prints "dep1:module, dep2:local" - not what we want!
26+
```
27+
28+
The problem was that `AppContextProvider[Dependency1]` defined in `Dependency1`'s companion object takes priority over the one extracted from `AppContextProviders`, because Scala's implicit search gives high priority to the companion object of the result type.
29+
30+
We had a workaround - `AppContextProviders.checkAllAreNeeded` - to detect such issues at compile time. But now we can solve the problem properly. I don't know why I missed this during writing a first variant, becouse now it looks obviously.
31+
32+
It turns out we can easy solve this problem by introducing an intermediate lookup type. If we search for a different type, which requere AppContextProvider[X], Scala compiler won't look in the companion object.
33+
34+
We introduce `AppContextProviderLookup[T]`:
35+
36+
```scala
37+
trait AppContextProviderLookup[T] {
38+
def get: T
39+
}
40+
41+
trait AppContextProviderLookupLowPriority {
42+
// Low priority fallback: delegate to AppContextProvider[T]
43+
given fromProvider[T](using provider: AppContextProvider[T]): AppContextProviderLookup[T] with {
44+
def get: T = provider.get
45+
}
46+
}
47+
48+
object AppContextProviderLookup extends AppContextProviderLookupLowPriority {
49+
// High priority: lookup from AppContextProviders in scope
50+
given fromProviders[Xs <: NonEmptyTuple, X, N <: Int](
51+
using providers: AppContextProvidersSearch[Xs],
52+
idx: TupleIndex.OfSubtype[Xs, X, N]
53+
): AppContextProviderLookup[X] with {
54+
def get: X = providers.getProvider[X, N].get
55+
}
56+
}
57+
```
58+
59+
Then we change `AppContext.apply` to use this new type:
60+
61+
```scala
62+
object AppContext {
63+
def apply[T](using AppContextProviderLookup[T]): T =
64+
summon[AppContextProviderLookup[T]].get
65+
}
66+
```
67+
68+
That's all.
69+
70+
When `AppContext[Dependency1]` is called inside a class with `AppContextProviders[(Dependency1, ...)]`:
71+
72+
1. Scala searches for `AppContextProviderLookup[Dependency1]`
73+
2. It looks in `AppContextProviderLookup`'s companion object (not `Dependency1`'s!)
74+
3. It finds `fromProviders` which requires `AppContextProvidersSearch[Xs]`
75+
4. The `AppContextProviders[(Dependency1, ...)]` in scope satisfies this requirement
76+
5. The value from `AppContextProviders` is used
77+
78+
When no `AppContextProviders` is in scope:
79+
80+
1. Scala searches for `AppContextProviderLookup[T]`
81+
2. `fromProviders` doesn't apply (no `AppContextProvidersSearch` available)
82+
3. Falls back to `fromProvider` which delegates to `AppContextProvider[T]`
83+
4. The companion-defined provider is used as expected
84+
85+
Now It Works as Expected
86+
87+
```scala
88+
class Component(using AppContextProviders[(Dependency1, Dependency2)]) {
89+
def doSomething(): String = {
90+
s"${AppContext[Dependency1].name}, ${AppContext[Dependency2].name}"
91+
}
92+
}
93+
94+
val dep1 = Dependency1("dep1:local")
95+
val dep2 = Dependency2("dep2:local")
96+
val c = Component(using AppContextProviders.of(dep1, dep2))
97+
println(c.doSomething()) // Now prints "dep1:local, dep2:local"!
98+
```
99+
100+
The values from `AppContextProviders` now take priority over companion-defined defaults, making dependency injection predictable.
101+
`checkAllAreNeeded` is no longer needed and has been removed.
102+

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# notes
2-
just place for some random notes about programming
2+
just place for some random notes about programming
33

4+
* 2025/12/01 [Solving the Implicit Search Priority Problem in AppContext](https://github.com/rssh/notes/blob/master/2025_12_01_implicit_search_priority.md)
45
* 2024/12/30 [small type-driven dependency injection in effect systems.](https://github.com/rssh/notes/blob/master/2024_12_30_dependency_injection_tf.md)
56
* 2024/12/09 [Small and simple type-driven dependency injection]( https://github.com/rssh/notes/blob/master/2024_12_09_dependency-injection.md)
67
* 2024/01/30 [Monad for backtracked logical progamming]( https://github.com/rssh/notes/blob/master/2024_01_30_logic-monad-1.md)

feed.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
<link>https://rssh.github.io/notes/feed.xml</link>
66
<description><![CDATA[random unsorted notes]]></description>
77
<docs>https://cyber.harvard.edu/rss/rss.html</docs>
8+
<item>
9+
<pubDate>Mon, 1 Dec 2025 00:00:00 +0200</pubDate>
10+
<author>Ruslan Shevchenko &lt;[email protected]&gt;</author>
11+
<description><![CDATA[at {}]]></description>
12+
<link>https://github.com/rssh/notes/blob/master/2025_12_01_implicit_search_priority.md</link>
13+
<title>Solving the Implicit Search Priority Problem in AppContext</title>
14+
</item>
815
<item>
916
<pubDate>Mon, 30 Dec 2024 00:00:00 +0200</pubDate>
1017
<author>Ruslan Shevchenko &lt;[email protected]&gt;</author>

0 commit comments

Comments
 (0)