jiale ko
jiale ko

Reputation: 157

Scala accept only String or Int generic case class in List

I have a case class defined as below

case class ChooseBoxData[T](index:T, text:String)

Is it possible to declare a List so that the list only accept type of ChooseBoxData[String] and ChooseBoxData[Int]?

What I expected is something like:

val specialList:List[some type declaration] = List(
  ChooseBoxData[String]("some string","some string"),/* allow, because is ChooseBoxData[String]*/
  ChooseBoxData[Int](12,"some string"), /* also allow, because is ChooseBoxData[Int]*/
  ChooseBoxData[Boolean](true,"some string")/* not allow type other than ChooseBoxData[String] or ChooseBoxData[Int]*/
)

Upvotes: 3

Views: 1366

Answers (4)

Dima
Dima

Reputation: 40510

Something like this maybe:

trait AllowableBoxData  
object AllowableBoxData {
private of[T](cbd: ChooseBoxData[T]) = new ChooseBoxData(cbd.index, cbd.text) 
    with AllowableBoxData
  implicit def ofInt(cbd: ChooseBoxData[Int]) = of(cbd)
  implicit def ofString(cbd: ChooseBoxData[String]) = of(cbd)
}

Now you can do things like

val list: List[ChooseBoxData[_] with AllowableBoxData] = List(ChooseBoxData("foo", "bar"), ChooseBoxData(0, "baz")

But not val list: List[AllowableBoxData] = List(ChooseBoxData(false, "baz"))

Also, if you were looking to declare a function argument rather than just a variable, there would be a bit more elegant solution:

trait CanUse[T]
implicit case object CanUseInt extends CanUse[Int]
implicit case object CanUseString extends CanUse[String]

def foo[T : CanUse](bar: List[ChooseBoxData[T]])

Upvotes: 1

jrook
jrook

Reputation: 3519

Here's what I came up with:

First, we create the following Algebraic Data Types (ADT):

sealed trait StringInt

case class Stringy(s : String) extends StringInt
case class Inty(s : Int) extends StringInt

And define ChoooseBoxData as follows:

case class ChooseBoxData(index : StringInt, text : String)

Then we define the following implicts to convert Int and String in the scope to the defined ADT:

object CBImplicits {
  implicit def conv(u : String) = Stringy(u)
  implicit def conv2(u : Int) = Inty(u)
}

Now, we can enforce the requirement in the question. Here is an example:

import CBImplicits._

val list = List(ChooseBoxData("str", "text"),
ChooseBoxData(1, "text"),
ChooseBoxData(true, "text"))

Trying to run the above, the compiler will complain about type mismatch. But this will compile and run:

List(
  ChooseBoxData("str", "text"),
  ChooseBoxData(1, "text"),
  ChooseBoxData(12, "text2"))

which results in:

a: List[ChooseBoxData] = 
List(ChooseBoxData(Stringy(str),text), ChooseBoxData(Inty(1),text), ChooseBoxData(Inty(12),text2))

This preserves index type information (wrapped in StringInt supertype of course) which later can be easily extracted using pattern matching for individual elements.

It is easy to remove the wrapper for all elements too, but it will result in the index type to become Any which is what we would expect because Any is the lowest common ancestor for both String and Int in Scala's class hierarchy.

EDIT: A Solution Using Shapeless

import shapeless._
import syntax.typeable._

case class ChooseBoxData[T](index : T, text : String)

val a = ChooseBoxData(1, "txt")
val b = ChooseBoxData("str", "txt")
val c = ChooseBoxData(true, "txt")
val list = List(a, b, c)

val `ChooseBoxData[Int]` = TypeCase[ChooseBoxData[Int]]
val `ChooseBoxData[String]` = TypeCase[ChooseBoxData[String]]

val res = list.map {
  case `ChooseBoxData[Int]`(u) => u
  case `ChooseBoxData[String]`(u) => u
  case _ => None
}
//result
res: List[Product with Serializable] = List(ChooseBoxData(1,txt), ChooseBoxData(str,txt), None)

So it allows compilation, but will replace invalid instances with None (which then can be used to throw a runtime error if desired), or you can directly filter the instances you want using:

list.flatMap(x => x.cast[ChooseBoxData[Int]])
//results in:
List[ChooseBoxData[Int]] = List(ChooseBoxData(1,txt))

Upvotes: 1

Raman Mishra
Raman Mishra

Reputation: 2686

Why can't you do it like this:

object solution extends App {

  case class ChooseBoxData[T](index: T, text: String) extends GenericType[T]

  trait GenericType[T] {
    def getType(index: T, text: String): ChooseBoxData[T] = ChooseBoxData[T](index, text)
  }

  val specialList = List(
    ChooseBoxData[String]("some string", "some string"),
    ChooseBoxData[Int](12, "some string"), 
    ChooseBoxData[Boolean](true, "some string")
  )

  println(specialList)
}

//output: List(ChooseBoxData(some string,some string), ChooseBoxData(12,some string), ChooseBoxData(true,some string))

Upvotes: -1

sarveshseri
sarveshseri

Reputation: 13985

You can build extra constraint on top of your case class.

import language.implicitConversions

case class ChooseBoxData[T](index:T, text:String)

trait MySpecialConstraint[T] {
  def get: ChooseBoxData[T]
}

implicit def liftWithMySpecialConstraintString(cbd: ChooseBoxData[String]) =
  new MySpecialConstraint[String] {
    def get = cbd
  }

implicit def liftWithMySpecialConstraintInt(cbd: ChooseBoxData[Int]) =
  new MySpecialConstraint[Int] {
    def get = cbd
  }


// Now we can just use this constraint for out list 
val l1: List[MySpecialConstraint[_]] = List(ChooseBoxData("A1", "B1"), ChooseBoxData(2, "B2"))

Upvotes: 0

Related Questions