36

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:

Previous ridiculous question:

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.

New premise:

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.

Intersection of implicit conversions?

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.

The current ridiculous question(s):

  1. Has anybody implemented this pattern anywhere before, or am I the first idiot to do it?
  2. Is there a much simpler way of doing this that I've blindly missed?
  3. If not, then how would I implement more general plumbing, such that I can write Seq[Foo & Bar & Baz]? This seems like a job for HList...
  4. Extra mega combo bonus: in implementing the more general plumbing, can I constrain the types to be unique? For example, I'd like to prohibit Seq[Foo & Foo].

The appendix of fails:

My latest attempt (gist). Not terrible, but there are two things I dislike there:

  • The Seq[All[A :: B :: C :: HNil]] syntax (I want the HList stuff to be opaque, and prefer Seq[A & B & C])
  • The explicit type annotation (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.
Community
  • 1
  • 1
mergeconflict
  • 8,156
  • 34
  • 63
  • 1
    This question is related to the "extra mega combo bonus": [enforce type difference](http://stackoverflow.com/questions/6909053/enforce-type-difference) – Aaron Novstrup Mar 23 '12 at 17:59
  • I'm not sure I see the connection between `HList` and what you're trying to do.... An HList is a list whose type maintains the type information about the constituent elements, whereas it sounds like you want to have a list wherein each constituent conforms to some fixed set of types. – Aaron Novstrup Mar 23 '12 at 23:22
  • @AaronNovstrup I'm trying to use something like `HList` as an arbitary-arity substitute for the fixed 2-arity `&[A, B]` class I defined. My goal is to have something that looks mostly like my `Seq[A & B]`, where the elements of the collection can be viewed as both `A` and `B` simultaneously, but again for an arbitrary number of types. – mergeconflict Mar 24 '12 at 00:01
  • @AaronNovstrup ... although I'm wondering if it might be more natural to encode this with some technique more like Miles's [unboxed union type](http://www.chuusai.com/2011/06/09/scala-union-types-curry-howard/) rather than `HList`. – mergeconflict Mar 24 '12 at 00:05
  • 1
    It's not really relevant to your question, but I just want to point out that `Seq[_ <: T]` is not the same as `Seq[T]`. `Seq[_ <: T]` is existential. – Owen Mar 24 '12 at 02:08
  • Would the trait (interface) model achieve the goal? http://www.scala-lang.org/node/126 – FlavorScape Mar 26 '12 at 16:49
  • @FlavorScape no, the goal is to avoid subtyping. – mergeconflict Mar 27 '12 at 17:17
  • @mergeconflict you crazy kids and your functional languages. – FlavorScape Mar 27 '12 at 19:08
  • To clarify what you actually want: You want a heterogeneous list that allows only types that are viewable as certain types? – Kaito Jun 20 '12 at 01:17
  • @Kaito: I want to populate a standard data structure (e.g. `Seq`) with objects viewable as every member of a given set of types. See the `Seq[Foo & Bar]` example above. `HList` comes into play when attempting to generalize to more than two views. – mergeconflict Jun 20 '12 at 02:39

1 Answers1

1

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

Edmondo
  • 19,559
  • 13
  • 62
  • 115