Showing posts with label Enumeration. Show all posts
Showing posts with label Enumeration. Show all posts

Friday, January 29, 2010

Overcoming Type Erasure in Matching 2 (Variance)

A commenter (Brian Howard) on the post Overcoming Type Erasure in Matching 1 made a very good point:

Is there a way to deal with some type arguments being contravariant? Try the following:

class A

class B extends A

val AAFunction = new Def[Function1[A,A]]

((a:A) => a) match {case AAFunction(f) => Some(f(new A)); case _ => None} // this is OK

((a:A) => new B) match {case AAFunction(f) => Some(f(new A)); case _ => None} // this is OK

((b:B) => b) match {case AAFunction(f) => Some(f(new A)); case _ => None} // gives a ClassCastException, since new A is not a B

There is a way to do this, however the information is not captured in the Manifest. A manifest is not designed to do full reflection it is by design very light and has little impact on performance. So to provide the functionality requested by Brian one needs to add that information to the Extractor Definition. Have have a possible solution below.
  1. scala> class A
  2. defined class A
  3. scala> class B extends A
  4. defined class B
  5. scala> object Variance extends Enumeration {
  6.      |     val Co, Contra, No = Value
  7.      | }
  8. defined module Variance
  9. scala> import Variance._
  10. import Variance._
  11. scala> class Def[C](variance:Variance.Value*)(implicit desired : Manifest[C]) {
  12.      |     def unapply[X](c : X)(implicit m : Manifest[X]) : Option[C] = {
  13.      |         val typeArgsTriplet = desired.typeArguments.zip(m.typeArguments).
  14.      |                                                     zipWithIndex
  15.      |         def sameArgs = typeArgsTriplet forall {
  16.      |             case ((desired,actual),index) if(getVariance(index) == Contra) => 
  17.      |                 desired <:< actual 
  18.      |             case ((desired,actual),index) if(getVariance(index) == No) => 
  19.      |                 desired == actual 
  20.      |             case ((desired,actual),index) => 
  21.      |                 desired >:> actual
  22.      |         }
  23.      |         
  24.      |         val isAssignable = desired.erasure.isAssignableFrom(m.erasure) || (desired >:> m)
  25.      |         if (isAssignable && sameArgs) Some(c.asInstanceOf[C])
  26.      |         else None
  27.      |     }
  28.      |     def getVariance(i:Int) = if(variance.length > i) variance(i) else No
  29.      | }
  30. defined class Def
  31. scala> val AAFunction = new Def[Function1[A,A]]
  32. AAFunction: Def[(A) => A] = Def@64a65760
  33. scala> ((a:A) => a) match {case AAFunction(f) => Some(f(new A)); case _ => None}
  34. res0: Option[A] = Some(A@1bd4f279)
  35. scala> ((a:A) => new B) match {case AAFunction(f) => Some(f(new A)); case _ => None}
  36. res1: Option[A] = None
  37. scala> ((b:B) => b) match {case AAFunction(f) => Some(f(new A)); case _ => None}
  38. res2: Option[A] = None
  39. scala> val BAFunction = new Def[Function1[B,A]](Contra,Co)
  40. BAFunction: Def[(B) => A] = Def@26114629
  41. scala> ((b:A) => b) match {case BAFunction(f) => Some(f(new B)); case _ => None}
  42. res3: Option[A] = Some(B@15dcc3ca)

Sunday, August 23, 2009

Enumerations

Scala does not have a enum keyword like java so enumerations are not quite as smooth. Depending on your requirements there are two ways to make enumerations.

  1. Create an object that extends the Enumeration class
  2. Create a case-object hierarchy.
I present both ways. Here are some tips:
  • If you only need discrete and related values without custom behaviour create and object that extends Enumeration
  • If each value has custom information associated with it use case-objects

In the following examples notice the use of the sealed keyword when defining the abstract class MyEnum. Sealed specifies that the heirarchy is sealed. Only classes defined in the same file can extend the class.

Also notice in the case object example that the enumeration values are "case object" not "case class". The two are similar except that there is only one instance of a case object. However all the same boiler plate code is generated and you can still match in the same way.

  1. // Note: MyEnum is an object NOT a class
  2. scala>object MyEnum extends Enumeration("one", "two", "three") {
  3.      | type MyEnumType = Value
  4.      | val One, Two, Three = Value
  5.      | }
  6. defined module MyEnum
  7. scala> MyEnum.One
  8. res1: MyEnum.Value = one
  9. scala>def printEnum( value:MyEnum.MyEnumType ) = println( value.toString )
  10. printEnum: (MyEnum.MyEnumType)Unit
  11. scala> printEnum(MyEnum.Two)
  12. two
  13. // If you don't want to prefix enums with MyEnum. Then you
  14. // can import the values.  This is similar to static imports in java
  15. scala>import MyEnum._
  16. import MyEnum._
  17. scala>def printEnum( value:MyEnumType ) = println( value.toString )
  18. printEnum: (MyEnum.MyEnumType)Unit
  19. scala> printEnum(Three)
  20. three
  21. // Similar but with cases objects
  22. // Notice MyEnum is 'sealed' and the parameters have the 'val' keyword so they are public
  23. scala> abstract sealed class MyEnum(val name:String, val someNum:Int)
  24. defined class MyEnum
  25. scala>caseobject One extends MyEnum("one", 1)
  26. defined module One
  27. scala>caseobject Two extends MyEnum("two", 2)
  28. defined module Two
  29. scala>caseobject Three extends MyEnum("three", 3)
  30. defined module Three
  31. scala>def printEnum(value:MyEnum) = println(value.name, value.someNum)
  32. printEnum: (MyEnum)Unit
  33. scala> printEnum(One)
  34. (one,1)
Subscribe to: Comments (Atom)

AltStyle によって変換されたページ (->オリジナル) /