Reputation: 1841
I'm finding that my code frequently looks a little like this:
trait Example {
def getThing1[A, O <: HList](a: A)(implicit g1: GetThing1[A] { type Out = O }): O = g1(a)
def getThing2[A, O <: HList](a: A)(implicit g2: GetThing2[A] { type Out = O }): O = g2(a)
def combineThings[T1 <: HList, T2 <: HList, O <: HList](t1: T1, t2: T2)(implicit
c: CombineThings[T1, T2] {type Out = O},
): O = c(t1, t2)
def getCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList, O <: HList](a: A)(implicit
g1: GetThing1[A] {type Out = T1},
g2: GetThing2[A] {type Out = T2},
c: CombineThings[T1, T2] {type Out = C},
r: Reverse[C] {type Out = O},
): O = r(combineThings(getThing1(a), getThing2(a)))
}
This is actually more complex than a stand-alone getCombinedReversed
method that uses implicits only and does not call the getThing1
, getThing2
or combineThings
methods:
def getCombinedReversedStandAlone[A, T1 <: HList, T2 <: HList, C <: HList, O <: HList](a: A)(implicit
g1: GetThing1[A] {type Out = T1},
g2: GetThing2[A] {type Out = T2},
c: CombineThings[T1, T2] {type Out = C},
r: Reverse[C] {type Out = O},
): O = r(c(g1(a), g2(a)))
I have no particular problem with this, but it does bloat out my code a bit, so I thought I'd check that there's no obvious solution. Obviously calling the getThing
and combineThings
methods without asserting that the correct implicit is in scope isn't possible.
Thanks for any assistance.
Upvotes: 0
Views: 81
Reputation: 51648
In implicit parameters of a method you can prefer Aux
-types rather than type refinements (you can automize generating Aux
types with a macro annotation from AUXify). Also in return type of a method you can prefer path-dependent type rather than additional type parameter (to be inferred).
def getThing1[A](a: A)(implicit g1: GetThing1[A]): g1.Out = g1(a)
def getThing2[A](a: A)(implicit g2: GetThing2[A]): g2.Out = g2(a)
def combineThings[T1 <: HList, T2 <: HList](t1: T1, t2: T2)(implicit
c: CombineThings[T1, T2]
): c.Out = c(t1, t2)
def getCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList](a: A)(implicit
g1: GetThing1.Aux[A, T1],
g2: GetThing2.Aux[A, T2],
c: CombineThings.Aux[T1, T2, C],
r: Reverse[C]
): r.Out = r(combineThings(getThing1(a), getThing2(a)))
def getCombinedReversedStandAlone[A, T1 <: HList, T2 <: HList, C <: HList](a: A)(implicit
g1: GetThing1.Aux[A, T1],
g2: GetThing2.Aux[A, T2],
c: CombineThings.Aux[T1, T2, C],
r: Reverse[C]
): r.Out = r(c(g1(a), g2(a)))
Besides that, regarding necessity to repeat implicit parameters please read
How to wrap a method having implicits with another method in Scala?
Pass implicit parameter through multiple objects
Generally speaking, writing your code like you described seems conventional. Implicit parameters help to understand the logic what method does (this surely demands some skill). If you start to hide implicits then your code can start to look less conventional :) If you repeat the same set of implicit parameters many times this is a signal to introduce a new type class.
import com.github.dmytromitin.auxify.macros.{aux, instance}
import shapeless.DepFn1
@aux @instance
trait GetCombinedReversed[A] extends DepFn1[A] {
type Out
def apply(a: A): Out
}
object GetCombinedReversed {
implicit def mkGetCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList](implicit
g1: GetThing1.Aux[A, T1],
g2: GetThing2.Aux[A, T2],
c: CombineThings.Aux[T1, T2, C],
r: Reverse[C]
): Aux[A, r.Out] = instance(a => r(c(g1(a), g2(a))))
}
def foo1[..., A, A1, ...](implicit ..., gcr: GetCombinedReversed.Aux[A, A1], ...) =
f(..., gcr(a), ...)
def foo2[..., A, A1, ...](implicit ..., gcr: GetCombinedReversed.Aux[A, A1], ...) =
g(..., gcr(a), ...)
In Scala 3 you can write
def getCombinedReversed[A, T1 <: HList, T2 <: HList, C <: HList](a: A)(using
g1: GetThing1[A],
g2: GetThing2[A],
c: CombineThings[g1.Out, g2.Out],
r: Reverse[c.Out]
): r.Out = ???
so type refinements or Aux
-types become necessary rarer although sometimes they are still necessary. I'll copy my comments from here:
def foo(using tc1: TC1[tc2.Out], tc2: TC2[tc1.Out]) = ???
doesn't compile while
def bar[A, B](using tc1: TC1.Aux[A, B], tc2: TC2.Aux[B, A]) = ???
and
def baz[A](using tc1: TC1[A], tc2: TC2.Aux[tc1.Out, A]) = ???
do.
Upvotes: 4