nobody nobody - 3 months ago 6
Scala Question

Passing function with Subclass as argument to function which takes any function with Super class as parameter

I have following scala code.

trait Super
case class Sub(value:String) extends Super
case class YetAnotherSub(value:String) extends Super
case class OnlyErrorType(value:String) extends Super

def function1[I <: Super, R](mapper : (I) => R, input: Super) (default: R): R = input match {
case error: OnlyErrorType =>
default

case success: I => mapper(success) // Ideally success => mapper(success)

case _ => default // I don't want this line at all, as I'm not expecting any other type
}

def function2(input:String):Super = if(input.size >= 3) Sub("Greater") else OnlyErrorType("Lesser")

def function3(input:String):String = {
val result = function2(input)
function1({sub:Sub => sub.value.toUpperCase}, result) ("Empty Result")
}

function3("Input")


There are various functions similar to
function2
which accepts some parameters and return any subtype of
Super
. I would like to have a generic mapper, like
function1
, to map type
Super
to some other type, but return a default value in case of
OnlyErrorType


In other words I would like to have some default handling for
OnlyErrorType
, but let calling function(in this case
function3
) specify the mapping for SuccessType (any sub types of
Super
except
OnlyErrorType
).

How do I achieve this ?

Above code compiles, but I hate to see warning,

warning: abstract type pattern I is unchecked since it is eliminated by erasure


I think there must be a better way to do this.

Answer

It's good you don't like the warning; in this case it basically means the test doesn't work.

The simplest approach is to make SuccessType explicit:

sealed trait Super
trait SuccessType extends Super
case class Sub(value:String) extends SuccessType
case class YetAnotherSub(value:String) extends SuccessType
case class OnlyErrorType(value:String) extends Super

def function1[R](mapper: SuccessType => R, input: Super) (default: R): R = input match {
  case _: OnlyErrorType => default
  case success: SuccessType => mapper(success)
}

Note that because Super is sealed, it can't be extended directly elsewhere, but one of its subtypes can be, so you can add new SuccessTypes. If that's not desired, make SuccessType sealed as well. Of course, in this case function1({case sub:Sub => sub.value.toUpperCase}, result) ("Empty Result") will fail if it's passed a YetAnotherSub. If that's not what you want, then you need to distinguish "Super which is Sub if successful" and "Super which is YetAnotherSub if successful" statically. You can use

sealed trait Super[I <: Super[I]]
case class Sub(value:String) extends Super[Sub]
case class YetAnotherSub(value:String) extends Super[YetAnotherSub]
case class OnlyErrorType[I <: Super[I]](value:String) extends Super[I]

def function1[I <: Super[I], R](mapper : (I) => R, input: Super[I]) (default: R): R = input match {
  case error: OnlyErrorType[_] =>
    default

  case success => mapper(success.asInstanceOf[I])
}

def function2(input:String):Super[Sub] = if(input.size >= 3) Sub("Greater") else OnlyErrorType("Lesser")

def function3(input:String):String = {
  val result = function2(input)
  function1({sub:Sub => sub.value.toUpperCase}, result) ("Empty Result")
}

function3("Input")

But I would prefer not to: the cast in function1 is actually safe, but it isn't trivially so (and defining new subtypes of Super incorrectly can break it).

Comments