Reputation: 330
I've got the following models:
case class Questionnaire(List(Question[_])
case class Question[A](text: String, Answer[A])
case class Answer[A](value: A)
I'm trying to set all of the answers on a questionnaire depending on their type (answers can be of type String, Double or LocalDate):
val questionnaire = getMockQuestionnaireWithoutAnswers()
questionnaire.questions.map {
case x: Question[String] => //...default the answer.value to some random string
case x: Question[Double] => //...default the answer.value to some random double
case x: Question[LocalDate] => //...default the answer.value to some random date
}
But I'm getting the error:
non-variable type argument String in type pattern examplenamespace.Question[String] is unchecked since it is eliminated by erasure.
I don't want to wrap all of the types into classes like this:
case class StringQuestion(question: Question[String])
What is the best way to avoid this error?
Upvotes: 4
Views: 5074
Reputation: 166
The accepted answer requires you to make every object inherit one of the traits. This solution doesn't require any changes to the object:
case class Answer[A](a:A)
case class Question[A](text: String, answer: Answer[A])
case class Questionnaire(questions: List[Question[_]])
def resolve(questions: List[Question[_]]): List[String] = {
questions.map {
case x: Question[String @unchecked] if x.answer.a.isInstanceOf[String] => "Stringy"
case x: Question[Double @unchecked] if x.answer.a.isInstanceOf[Double] => "Doubly"
case x: Question[Int @unchecked] if x.answer.a.isInstanceOf[Int] => "Inty"
}
}
Basically find a variable with your class that exposes the type and check its type. The @unchecked removes the warning the compiler generates.
Upvotes: 1
Reputation: 37435
Type erasure is eliminating the parametric type provided in the match expression, meaning that the difference between Question[String]
and Question[Int]
gets lost at runtime.
One way to circumvent this is to provide concrete implementations of that type, sothat the JVM cannot "erase" it.
I've used this construction in the past. There're probably other ways, but this one is simple enough:
case class Answer[A](a:A)
case class Question[A](text: String, answer: Answer[A])
case class Questionnaire(questions: List[Question[_]])
// concrete types
trait StringQuestion extends Question[String]
trait DoubleQuestion extends Question[Double]
trait IntQuestion extends Question[Int]
def resolve(questions: List[Question[_]]): List[String] = {
questions.map {
case x: StringQuestion => "Stringy"
case x: DoubleQuestion => "Doubly"
case x: IntQuestion => "Inty"
}
}
Upvotes: 6
Reputation: 8866
Try this approach:
questionnaire.questions.map {
case Question(text, Answer(d:Double)) => //
case Question(text, Answer(s:String)) => //
case Question(text, Answer(ld:LocalDate)) => //
}
You have access to text of Question (text) and value of Answer (d, s or ld)
You also can use aliases for variables:
questionnaire.questions.map {
case q @ Question(text, a @ Answer(d:Double)) => //
case q @ Question(text, a @ Answer(s:String)) => //
case q @ Question(text, a @ Answer(ld:LocalDate)) => //
}
Upvotes: 5