Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extracting a value of a given type from a case class

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.

Update

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?

like image 810
Noel M Avatar asked Nov 15 '14 22:11

Noel M


People also ask

What are case classes?

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.

What is the use of case class in spark?

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 does case class mean in Scala?

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.

What is case class in Scala syntax of case class?

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.


1 Answers

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
like image 134
Miles Sabin Avatar answered Oct 22 '22 13:10

Miles Sabin