1
- class Arg [T ]
1
+ /* These tests show various mechanisms available for implicit prioritization.
2
+ */
2
3
3
- // Traditional scheme: use location in class hierarchy
4
+ class E [ T ]( val str : String ) // The type for which we infer terms below
4
5
5
- class E [T ]( val str : String )
6
+ class Arg [T ] // An argument that we use as a given for some implied instances below
6
7
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
+ */
7
13
class LowPriorityImplicits {
8
14
implied t1[T ] for E [T ](" low" )
9
15
}
@@ -14,18 +20,19 @@ object NormalImplicits extends LowPriorityImplicits {
14
20
15
21
def test1 = {
16
22
import implied NormalImplicits ._
17
- assert(the[E [String ]].str == " low" )
23
+ assert(the[E [String ]].str == " low" ) // No Arg available, so only t1 applies
18
24
19
25
{ implied for Arg [String ]
20
- assert(the[E [String ]].str == " norm" )
26
+ assert(the[E [String ]].str == " norm" ) // Arg available, t2 takes priority
21
27
}
22
28
}
23
29
24
- // Priority arguments:
25
-
30
+ /* New scheme: dummy implicit arguments that indicate priorities
31
+ */
26
32
object Priority {
27
33
class Low
28
34
object Low { implied for Low }
35
+
29
36
class High extends Low
30
37
object High { implied for High }
31
38
}
@@ -37,17 +44,21 @@ object Impl2 {
37
44
38
45
def test2 = {
39
46
import implied Impl2 ._
40
- assert(the[E [String ]].str == " low" )
47
+ assert(the[E [String ]].str == " low" ) // No Arg available, so only t1 applies
41
48
42
49
{ implied for Arg [String ]
43
- assert(the[E [String ]].str == " norm" )
50
+ assert(the[E [String ]].str == " norm" ) // Arg available, t2 takes priority
44
51
}
45
52
}
46
53
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
+ */
51
62
object Impl2a {
52
63
implied t3[T ] for E [T ](" hi" )
53
64
}
@@ -60,55 +71,91 @@ def test2a = {
60
71
assert(the[E [String ]].str == " hi" )
61
72
}
62
73
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
+ */
65
77
object Impl3 {
66
78
implied t1[T ] for E [T ](" low" )
67
79
}
68
80
69
81
object Override {
70
- trait HighestPriority
82
+ trait HighestPriority // A marker trait to indicate a higher priority
71
83
72
84
implied over[T ] for E [T ](" hi" ), HighestPriority
73
85
}
74
86
75
87
def test3 = {
76
88
import implied Impl3 ._
77
- assert(the[E [String ]].str == " low" )
89
+ assert(the[E [String ]].str == " low" ) // only t1 is available
78
90
79
91
{ 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.
81
94
}
82
95
}
83
96
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
+ */
85
103
object Impl4 {
86
104
implied t1 for E [String ](" string" )
87
105
implied t2[T ] given Arg [T ] for E [T ](" generic" )
88
106
}
89
107
90
- object fb {
108
+ object fallback4 {
91
109
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
93
110
}
94
111
95
112
def test4 = {
96
113
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
100
117
101
118
{ implied for Arg [Int ]
102
- assert(withFallback[Int ].str == " generic" )
119
+ assert(withFallback[Int ].str == " generic" ) // t2 is applicable
103
120
}
104
121
}
105
122
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
+ }
106
152
107
153
object Test extends App {
108
154
test1
109
155
test2
110
156
test2a
111
157
test3
112
158
test4
159
+ test5
113
160
}
114
161
0 commit comments