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+ */
713class LowPriorityImplicits {
814 implied t1[T ] for E [T ](" low" )
915}
@@ -14,18 +20,19 @@ object NormalImplicits extends LowPriorityImplicits {
1420
1521def 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+ */
2632object 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
3845def 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+ */
5162object 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+ */
6577object Impl3 {
6678 implied t1[T ] for E [T ](" low" )
6779}
6880
6981object 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
7587def 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+ */
85103object 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
95112def 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
107153object Test extends App {
108154 test1
109155 test2
110156 test2a
111157 test3
112158 test4
159+ test5
113160}
114161
0 commit comments