Okay, fair warning: this is a follow-up to my ridiculous question from last week. Although I think this question isn't as ridiculous. Anyway, here goes:
Assume I have some base trait T
with subclasses A
, B
and C
, I can declare a collection Seq[T]
for example, that can contain values of type A
, B
and C
. Making the subtyping more explicit, let's use the Seq[_ <: T]
type bound syntax.
Now instead assume I have a typeclass TC[_]
with members A
, B
and C
(where "member" means the compiler can find some TC[A]
, etc. in implicit scope). Similar to above, I want to declare a collection of type Seq[_ : TC]
, using context bound syntax.
This isn't legal Scala, and attempting to emulate may make you feel like a bad person. Remember that context bound syntax (when used correctly!) desugars into an implicit parameter list for the class or method being defined, which doesn't make any sense here.
So let's assume that typeclass instances (i.e. implicit values) are out of the question, and instead we need to use implicit conversions in this case. I have some type V
(the "v" is supposed to stand for "view," fwiw), and implicit conversions in scope A => V
, B => V
and C => V
. Now I can populate a Seq[V]
, despite A
, B
and C
being otherwise unrelated.
But what if I want a collection of things that are implicitly convertible both to views V1
and V2
? I can't say Seq[V1 with V2]
because my implicit conversions don't magically aggregate that way.
I solved my problem like this:
// a sort of product or intersection, basically identical to Tuple2
final class &[A, B](val a: A, val b: B)
// implicit conversions from the product to its member types
implicit def productToA[A, B](ab: A & B): A = ab.a
implicit def productToB[A, B](ab: A & B): B = ab.b
// implicit conversion from A to (V1 & V2)
implicit def viewsToProduct[A, V1, V2](a: A)(implicit v1: A => V1, v2: A => V2) =
new &(v1(a), v2(a))
Now I can write Seq[V1 & V2]
like a boss. For example:
trait Foo { def foo: String }
trait Bar { def bar: String }
implicit def stringFoo(a: String) = new Foo { def foo = a + " sf" }
implicit def stringBar(a: String) = new Bar { def bar = a + " sb" }
implicit def intFoo(a: Int) = new Foo { def foo = a.toString + " if" }
implicit def intBar(a: Int) = new Bar { def bar = a.toString + " ib" }
val s1 = Seq[Foo & Bar]("hoho", 1)
val s2 = s1 flatMap (ab => Seq(ab.foo, ab.bar))
// equal to Seq("hoho sf", "hoho sb", "1 if", "1 ib")
The implicit conversions from String
and Int
to type Foo & Bar
occur when the sequence is populated, and then the implicit conversions from Foo & Bar
to Foo
and Bar
occur when calling foobar.foo
and foobar.bar
.
Seq[Foo & Bar & Baz]
? This seems like a job for HList
...Seq[Foo & Foo]
.My latest attempt (gist). Not terrible, but there are two things I dislike there:
Seq[All[A :: B :: C :: HNil]]
syntax (I want the HList
stuff to be opaque, and prefer Seq[A & B & C]
)abc[A].a
) required for conversion. It seems like you can either have type inference or implicit conversions, but not both... I couldn't figure out how to avoid it, anyhow.I can give a partial answer for the point 4. This can be obtained by applying a technique such as :
http://vpatryshev.blogspot.com/2012/03/miles-sabins-type-negation-in-practice.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With