1

Given that i have a kind of enumeration using a sealed trait and case objects representing the values, is it possible to enforce a mechanism to retrieve the single unique value for a given type, without requiring an implicit argument?

with implicits this would be

sealed trait Enum
sealed trait Value1 extends Enum
case object Value1 extends Value1 { implicit val me: Value1 = Value1 }
sealed trait Value2 extends Enum
case object Value2 extends Value2 { implicit val me: Value1 = Value1 }

def test[V <: Enum](implicit evidence: V): V = evidence

test[Value1]

is it possible to drop the implicit argument to test? that is, to ensure that V is a defined subtype of Enum (obviously, test[Enum] should fail). the gordian knot is:

object Enum {
  def unique[V <: Enum]: V = ???
}

?

0__
  • 66,707
  • 21
  • 171
  • 266
  • I don't know what you are trying to accomplish with this trickery. Why can't you just use a Java Type-Safe Enum? – Y.H Wong Mar 01 '11 at 08:26
  • @yhwong: I would like to be able to create a method such as def `fill[R <: Rate](n: Int)(fun: => GE[R]) : Mix[R]` where `trait GE[R <: Rate] { def rate: R }` and `trait Mix[R <: Rate] extends GE[R]` such that I can call `fill` with `n == 0` and still be able to construct `Mix` despite not being able to retrieve a value for `R` from any call to `fun`. Adding an implicit value of `R` causes some problems with type inference when another indirection is involved, e.g. `def disjoint[R <: Rate, G <: GE[R]](n: Int)(fun: => G): G` – 0__ Mar 01 '11 at 13:00
  • @yhwong: Another explanation "why": Ideally i wouldn't require `GE` to provide a value for `R`, this could be left to concrete instances of `GE`. However, I need to be able to call binary operators on `GE` which construct a target `GE` whose type parameter `R` depends on the operands' type parameters, such that for instance `(a: GE[audio]) * (b: GE[control])` yields `HigherRate[audio, control]`. While this can all be done without ever passing around a value for `R`, i want to be able to allow relaxed dynamic typing, such that i can do `someGE.rate match { ... }` – 0__ Mar 01 '11 at 13:14

1 Answers1

1

It looks as though you're trying to look up specific values or behaviour based on type and, for whatever reason don't want to put this behaviour in some common superclass.

It also looks as though you're trying to use implicits and companion objects to achieve this by emulating some of the characteristics of Java's statics. This is a model you absolutely want to be dropping from your mental toolkit, it's hopelessly broken from the perspective of object oriented programming.

What you really want is ad-hoc polymorphism, and we have better ways to do that. Interestingly, it does still use implicits, and a context bound:

sealed trait Enum
sealed trait Value1 extends Enum
sealed trait Value2 extends Enum

case object Value1 extends Value1
case object Value2 extends Value2

sealed abstract class UniqValue[T] { def value: T }
implicit object Value1HasUniq extends UniqValue[Value1] { val value = Value1 }
implicit object Value2HasUniq extends UniqValue[Value2] { val value = Value2 }

def test[V <: Enum : UniqValue]: V = implicitly[UniqValue[V]].value

You could also define test as:

def test[V <: Enum](implicit ev: UniqValue[V]): V = ev.value

Which does exactly the same thing, manually removing the syntactic sugar of context bounds that the compiler would do for you anyway.

Basically, it all works because the compile will also consider type params when resolving implicits.

If you want to read up more on this kind of thing, the magic prhrase to search for is "type classes"

Kevin Wright
  • 49,540
  • 9
  • 105
  • 155
  • thanks, kevin. can you tell me why the above is better, i.e. why it is better to ask for an indirect implicit taking the Enum as a type parameter rather than asking for an implicit value of Enum? in terms of method signature of test, i still see that an implicit evidence parameter is required, so i wonder what is the advantage of your approach? – 0__ Mar 01 '11 at 12:51
  • 1
    The flippant answer is that "my approach is better because it actually works". With your approach, you were passing a type to a function, then expecting that function to somehow locate a singleton of that type. This is logically equivalent to having a label on a crate stating "open with the crowbar that you'll find inside", I just left the crowbar on the outside... You'll find this pattern widely used in Scala, `Numeric` is a good example. – Kevin Wright Mar 01 '11 at 13:33
  • The other advantage is that you can use it to look up any type from any other type, it isn't only restricted to companion class/object pairs - which is actually a very unusual arrangement for type classes. – Kevin Wright Mar 01 '11 at 13:37