I call my attempt at a better version the "stuttering-or" method, and it looks like this:
package org.okcjug.typeerasure.arrrgh.util
case class DisjointType[A,B](val a: Option[A], val b: Option[B])
object DisjointType {
type or[A,B] = DisjointType[A,B] // who wants to type "DisjointType" all the time?
// convenience of definition functions
private def da[A,B](a: A): or[A,B] = { DisjointType(Some(a),None) }
private def db[A,B](b: B): or[A,B] = { DisjointType(None,Some(b)) }
// implicit defs - stuttering-or
implicit def aToDisjointType2[A,B](a: A): or[A,B] =
{ da(a) }
implicit def bToDisjointType2[A,B](b: B): or[A,B] =
{ db(b) }
implicit def aToDisjointType3[A,B,C](a: A): or[or[A,B],C] =
{ da(da(a)) }
implicit def bToDisjointType3[A,B,C](b: B): or[or[A,B],C] =
{ da(db(b)) }
implicit def aToDisjointType4[A,B,C,D](a: A): or[or[or[A,B],C],D] =
{ da(da(da(a))) }
implicit def bToDisjointType4[A,B,C,D](b: B): or[or[or[A,B],C],D] =
{ da(da(db(b))) }
implicit def aToDisjointType5[A,B,C,D,E](a: A): or[or[or[or[A,B],C],D],E] =
{ da(da(da(da(a)))) }
implicit def bToDisjointType5[A,B,C,D,E](b: B): or[or[or[or[A,B],C],D],E] =
{ da(da(da(db(b)))) }
}
The "magic" part of this is that it allows infix type notation to represent "Type1 or Type2" as "DisjointType[Type1,Type2]". Then, the implicit defs allow the component types to be represented in a type view. This guards the methods that define it to only accept members of the component types. Note that it is up to the method implementer to cover all the possible cases as the type system will not help you there.
Defining the methods is something like this:
package org.okcjug.typeerasure.arrrgh.work
import org.okcjug.typeerasure.arrrgh.util.DisjointType._
class Foo {
def erasureMethod[T <% Int or String or Double](t: Option[T]) = {
t match {
case Some(x: Int) => println("processing Int: " + x)
case Some(x: String) => println("processing String: " + x)
case Some(x: Double) => println("processing Double: " + x)
case None => println("empty and I don't care the type")
}
}
def erasureMethod2[T <% String or Int](lt: List[T]) = {
for (x <- lt) x match {
case x: String => println("String list item: " + x)
case x: Int => println("Int list item: " + x)
}
}
}
And, the calling code looks like this:
package org.okcjug.typeerasure.arrrgh
import org.okcjug.typeerasure.arrrgh.work.Foo
import org.okcjug.typeerasure.arrrgh.util.DisjointType._
object App extends Application {
val f = new Foo()
f.erasureMethod(Some("blah"))
// f.erasureMethod(None) // compiler error
f.erasureMethod(None.asInstanceOf[Option[String]]) // ok
f.erasureMethod(Some(42))
// f.erasureMethod(Some(List("b"))) compiler error
f.erasureMethod2(List("a","b","c"))
f.erasureMethod2(List(5,4,3,2))
// f.erasureMethod2(List(4.3)) compiler error
}
So, what does this get me? Well, implementing the code is fairly straightforward, just using a "Type1 or Type2 or Type3 ..." construct for the disjoint types (up to 5 in the given code). Also, discerning the specific type within the polymorphic method for purposes of dispatch is done using the standard "match" keyword.
What I don't like about my solution is that the calling code must import the implicit methods (here: org.okcjug.typeerasure.arrrgh.util.DisjointType._). Each of the the compared methods from the top of this article also required importing the implicit methods. However, I think my approach shows more promise because the implicit defs that need to be imported are not specific to the pseudo-polymorphic methods I'm implementing. In fact, the content of this import is suitable to be included in the Predef object.
Maybe I'll poke around the scala mailing list and see if any library maintainers want to oblige me...
3 comments:
Very nice. I like your use of "or" as a type infix operator, I have been looking for examples.
I inclination not agree on it. I think warm-hearted post. Expressly the title attracted me to read the sound story.
Amiable brief and this fill someone in on helped me alot in my college assignement. Thank you seeking your information.
Post a Comment