Is it possible, using Shapeless, to extract a value of a specific type from a case class? So far, I can do this:
def fromCaseClass[T, R <: HList](value: T)(implicit ga: Generic.Aux[T, R]): R = {
ga.to(value)
}
Which then allows me to extract values procedurally:
scala> case class ServiceConfig(host: String, port: Int, secure: Boolean)
defined class ServiceConfig
scala> val instance = ServiceConfig("host", 80, true)
instance: ServiceConfig = ServiceConfig(host,80,true)
scala> fromCaseClass(instance).select[Boolean]
res10: Boolean = true
scala> fromCaseClass(instance).select[Int]
res11: Int = 80
However, when I try to write a function to do this, I'm getting tied up with implicits not found:
def getByType[C, X](value: C)(implicit ga: Generic.Aux[C, X]): X = {
fromCaseClass(value).select[X]
}
<console>:12: error: could not find implicit value for parameter ga: shapeless.Generic.Aux[C,R]
fromCaseClass(value).select[X]
Presumably I'm getting this because the compiler can't verify that my parameter is not a case class. Is there a way for me to do this?
I'm quite new to Shapeless, so I'm not entirely sure if I'm trying to do something crazy or if missing something simple.
I feel I'm getting a bit closer. I can implement as so:
def newGetByType[C, H <: HList, R]
(value: C)
(implicit ga: Generic.Aux[C, H], selector: Selector[H, R]): R = {
ga.to(value).select[R]
}
And this allows me to select from a case class:
scala> val s: String = newGetByType(instance)
s: String = host
But this only seems to work for the first type in the case class:
scala> val i: Int = newGetByType(instance)
<console>:17: error: type mismatch;
found : String
required: Int
val i: Int = newGetByType(instance)
Am I on the right track?
A case class has all of the functionality of a regular class, and more. When the compiler sees the case keyword in front of a class , it generates code for you, with the following benefits: Case class constructor parameters are public val fields by default, so accessor methods are generated for each parameter.
The Scala interface for Spark SQL supports automatically converting an RDD containing case classes to a DataFrame. The case class defines the schema of the table. The names of the arguments to the case class are read using reflection and they become the names of the columns.
What is Scala Case Class? A Scala Case Class is like a regular class, except it is good for modeling immutable data. It also serves useful in pattern matching, such a class has a default apply() method which handles object construction. A scala case class also has all vals, which means they are immutable.
Scala case classes are just regular classes which are immutable by default and decomposable through pattern matching. It uses equal method to compare instance structurally. It does not use new keyword to instantiate object. All the parameters listed in the case class are public and immutable by default. Syntax.
You were getting close ...
The main problem with your newGetByType
is that it doesn't give you any way to explicitly specify the type you're hoping to extract (and, as you've observed, typing the val on the LHS isn't sufficient to allow it to be inferred).
Why can't you explicitly specify the type to be extracted? Here's the definition again,
def getByType[S, C, L <: HList](value: C)
(implicit gen: Generic.Aux[C, L], sel: Selector[L, S]): S =
gen.to(value).select[S]
Ideally we'd like to be able to specify the type argument S
, allow C
to be inferred from the value
argument, and L
to be computed by implicit resolution from C
. Unfortunately, however, Scala doesn't allow us to partially specify type arguments ... it's all or nothing.
So the trick to get this to work is to split the type parameter list into two: one which can be fully specified explicitly and one which can be fully inferred: this is a general technique, not one which is specific to shapeless.
We do this by moving the main part of the computation to an auxiliary class which is parametrized by the type we are going to supply explicitly,
class GetByType[S] {
def apply[C, L <: HList](value: C)
(implicit gen: Generic.Aux[C, L], sel: Selector[L, S]): S =
gen.to(value).select[S]
}
Notice that here we can now assume that S
is known, and both of the type arguments to apply
can be inferred. We finish up with the now trivial definition of getByType
which just provides a place where the explicit type argument can go, and instantiates the auxiliary class,
def getByType[S] = new GetByType[S]
This gives you the result you want,
scala> import shapeless._, ops.hlist._
import ops.hlist._
scala> :paste
// Entering paste mode (ctrl-D to finish)
class GetByType[S] {
def apply[C, L <: HList](value: C)
(implicit gen: Generic.Aux[C, L], sel: Selector[L, S]): S =
gen.to(value).select[S]
}
// Exiting paste mode, now interpreting.
defined class GetByType
scala> def getByType[S] = new GetByType[S]
getByType: [S]=> GetByType[S]
scala> case class ServiceConfig(host: String, port: Int, secure: Boolean)
defined class ServiceConfig
scala> val instance = ServiceConfig("host", 80, true)
instance: ServiceConfig = ServiceConfig(host,80,true)
scala> getByType[String](instance)
res0: String = host
scala> getByType[Int](instance)
res1: Int = 80
scala> getByType[Boolean](instance)
res2: Boolean = true
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With