0

I'm tying to write a generic function that will convert any type to Option[T] but somehow it's not working as expected

scala> def toOption[T](obj:Any):Option[T] = obj match {
 |   case obj:T => Some(obj)
 |   case _ => None
 | }
<console>:11: warning: abstract type pattern T is unchecked since it is eliminated by erasure
     case obj:T => Some(obj)
              ^
 toOption: [T](obj: Any)Option[T]

here it's seems ok it return the Option[String]

scala> toOption[String]("abc")
res0: Option[String] = Some(abc)

but here it return Some(def) instead of None

scala> toOption[Int]("def")
res1: Option[Int] = Some(def)

i can't seem to figure the appropriate way to create this generic function ,nor to understand why is that happens, I've already read many posts and questions about type erasure in scala but still can't get it, specific explanation would be a great help!

oblivion
  • 5,928
  • 3
  • 34
  • 55
superDoss
  • 61
  • 6

2 Answers2

2

The type T is not available at runtime because Scala uses type erasure for generics like Java does. So your test case obj:T has not the desired effects and the compiler warned you about that fact.

You may use ClassTags to make the type information available at runtime. ClassTag already implements the conversion to Optional in its unapply method:

scala> import scala.reflect.{classTag,ClassTag};
import scala.reflect.{classTag, ClassTag}

scala> def toOption[T: ClassTag](obj: Any): Option[T] = classTag[T].unapply(obj);
toOption: [T](obj: Any)(implicit evidence$1: scala.reflect.ClassTag[T])Option[T]

scala> toOption[Int](1)
res1: Option[Int] = Some(1)

scala> toOption[String](1)
res2: Option[String] = None

scala> toOption[String]("one")
res3: Option[String] = Some(one)

scala> toOption[Int]("one")
res4: Option[Int] = None

But this doesn't work for generic types as you see here:

scala> toOption[List[Int]](List("one", "two"))
res5: Option[List[Int]] = Some(List(one, two))

scala> res5.get
res6: List[Int] = List(one, two)

scala> res6(0) + res6(1)
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
  at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
  ... 29 elided
Daniel Faber
  • 351
  • 2
  • 3
  • OK I understand now a bit more ,but let's say I'll try this `scala> def toOption[T <: Any](obj:Any):Option[T] = obj match { | case obj: String => Some(obj) | case _ => None ` will it work? – superDoss Jan 24 '17 at 23:06
0

You might want to have two type arguments of the function - the desired type and the actual type:

def toOption[Desired, Actual](obj:Actual):Option[Desired] = ???

And then, I guess, you want to return Some only if the types match:

def toOption[Desired, Actual](obj:Actual)
  (implicit ev: Actual =:= Desired = null):Option[Desired] = 
  if(ev == null) 
    None
  else
    Some(ev(obj))

Here you have two type arguments, that might be inconvenient sometimes, when Scala cannot infer both of the arguments. For the same syntax as you used (giving single type argument), you may use the following trick:

class toOptionImpl[Desired] {
  def apply[Desired, Actual](obj:Actual)
    (implicit ev: Actual =:= Desired = null):Option[Desired] = 
    if(ev == null) 
      None
    else
      Some(ev(obj))
}
def toOption[Desired] = new toOptionImpl[Desired]

It might be used the same way:

toOption[String]("def") == Some("def")
toOption[Int]("def") == None

(You might also look into Miles Sabin's polymorphic functions https://milessabin.com/blog/2012/04/27/shapeless-polymorphic-function-values-1/, https://milessabin.com/blog/2012/05/10/shapeless-polymorphic-function-values-2/)

Arseniy Zhizhelev
  • 2,381
  • 17
  • 21