Skip to content

Commit 0d1d963

Browse files
committed
Flesh out and document priority test
1 parent 005ad50 commit 0d1d963

File tree

1 file changed

+72
-25
lines changed

1 file changed

+72
-25
lines changed

tests/run/implied-priority.scala

Lines changed: 72 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
class Arg[T]
1+
/* These tests show various mechanisms available for implicit prioritization.
2+
*/
23

3-
// Traditional scheme: use location in class hierarchy
4+
class E[T](val str: String) // The type for which we infer terms below
45

5-
class E[T](val str: String)
6+
class Arg[T] // An argument that we use as a given for some implied instances below
67

8+
/* First, two schemes that require a pre-planned architecture for how and
9+
* where implied instances are defined.
10+
*
11+
* Traditional scheme: prioritize with location in class hierarchy
12+
*/
713
class LowPriorityImplicits {
814
implied t1[T] for E[T]("low")
915
}
@@ -14,18 +20,19 @@ object NormalImplicits extends LowPriorityImplicits {
1420

1521
def test1 = {
1622
import implied NormalImplicits._
17-
assert(the[E[String]].str == "low")
23+
assert(the[E[String]].str == "low") // No Arg available, so only t1 applies
1824

1925
{ implied for Arg[String]
20-
assert(the[E[String]].str == "norm")
26+
assert(the[E[String]].str == "norm") // Arg available, t2 takes priority
2127
}
2228
}
2329

24-
// Priority arguments:
25-
30+
/* New scheme: dummy implicit arguments that indicate priorities
31+
*/
2632
object Priority {
2733
class Low
2834
object Low { implied for Low }
35+
2936
class High extends Low
3037
object High { implied for High }
3138
}
@@ -37,17 +44,21 @@ object Impl2 {
3744

3845
def test2 = {
3946
import implied Impl2._
40-
assert(the[E[String]].str == "low")
47+
assert(the[E[String]].str == "low") // No Arg available, so only t1 applies
4148

4249
{ implied for Arg[String]
43-
assert(the[E[String]].str == "norm")
50+
assert(the[E[String]].str == "norm") // Arg available, t2 takes priority
4451
}
4552
}
4653

47-
// Adding an override to an existing hierarchy:
48-
// If all of the alternatives in the existing hierarchy take implicit arguments,
49-
// an alternative without implicit arguments would override all of them.
50-
54+
/* The remaining tests show how we can add an override of highest priority or
55+
* a fallback of lowest priority to a group of existing implied instances, without
56+
* needing to change the location or definition of those instances.
57+
*
58+
* First, consider the problem how to define an override of highest priority.
59+
* If all of the alternatives in the existing hierarchy take implicit arguments,
60+
* an alternative without implicit arguments would override all of them.
61+
*/
5162
object Impl2a {
5263
implied t3[T] for E[T]("hi")
5364
}
@@ -60,55 +71,91 @@ def test2a = {
6071
assert(the[E[String]].str == "hi")
6172
}
6273

63-
// If not, we can use result refinement:
64-
74+
/* If that solution is not applicable, we can define an override by refining the
75+
* result type of the implied instance, e.g. like this:
76+
*/
6577
object Impl3 {
6678
implied t1[T] for E[T]("low")
6779
}
6880

6981
object Override {
70-
trait HighestPriority
82+
trait HighestPriority // A marker trait to indicate a higher priority
7183

7284
implied over[T] for E[T]("hi"), HighestPriority
7385
}
7486

7587
def test3 = {
7688
import implied Impl3._
77-
assert(the[E[String]].str == "low")
89+
assert(the[E[String]].str == "low") // only t1 is available
7890

7991
{ import implied Override._
80-
assert(the[E[String]].str == "hi")
92+
import implied Impl3._
93+
assert(the[E[String]].str == "hi") // `over` takes priority since its result type is a subtype of t1's.
8194
}
8295
}
8396

84-
// Adding a fallback to an existing hierarchy:
97+
/* Now consider the dual problem: How to install a fallback with lower priority than existing
98+
* implied instances that kicks in when none of the other instances are applicable.
99+
* We get there in two stages. The first stage is by defining an explicit `withFallback` method
100+
* that takes the right implicit and returns it. This can be achieved using an implicit parameter
101+
* with a default argument.
102+
*/
85103
object Impl4 {
86104
implied t1 for E[String]("string")
87105
implied t2[T] given Arg[T] for E[T]("generic")
88106
}
89107

90-
object fb {
108+
object fallback4 {
91109
def withFallback[T] given (ev: E[T] = new E[T]("fallback")): E[T] = ev
92-
implied [T] given (ev: E[T] = new E[T]("fallback")) for E[T] = ev
93110
}
94111

95112
def test4 = {
96113
import implied Impl4._
97-
import fb._
98-
assert(withFallback[String].str == "string")
99-
assert(withFallback[Int].str == "fallback")
114+
import fallback4._
115+
assert(withFallback[String].str == "string") // t1 is applicable
116+
assert(withFallback[Int].str == "fallback") // No applicable instances, pick the default
100117

101118
{ implied for Arg[Int]
102-
assert(withFallback[Int].str == "generic")
119+
assert(withFallback[Int].str == "generic") // t2 is applicable
103120
}
104121
}
105122

123+
/* The final setup considers the problem how to define a fallback with lower priority than existing
124+
* implicits that exists as an implicit instance alongside the others. This can be achieved
125+
* by combining the implicit parameter with default technique for getting an existing impplicit
126+
* or a fallback with the result refinement technique for overriding all existing implicit instances.
127+
*
128+
* It employs a more re-usable version of the result refinement trick.
129+
*/
130+
opaque type HigherPriority = Any
131+
object HigherPriority {
132+
def inject[T](x: T): T & HigherPriority = x
133+
}
134+
135+
object fallback5 {
136+
implied [T] given (ev: E[T] = new E[T]("fallback")) for (E[T] & HigherPriority) = HigherPriority.inject(ev)
137+
}
138+
139+
def test5 = {
140+
import implied Impl4._
141+
import implied fallback5._
142+
143+
// All inferred terms go through the implied instance in fallback5.
144+
// They differ in what implicit argument is synthesized for that instance.
145+
assert(the[E[String]].str == "string") // t1 is applicable
146+
assert(the[E[Int]].str == "fallback") // No applicable instances, pick the default
147+
148+
{ implied for Arg[Int]
149+
assert(the[E[Int]].str == "generic") // t2 is applicable
150+
}
151+
}
106152

107153
object Test extends App {
108154
test1
109155
test2
110156
test2a
111157
test3
112158
test4
159+
test5
113160
}
114161

0 commit comments

Comments
 (0)