What is the best way to extract a type from a data container such as a case class.
For example if I have a type Tagged[U] = { type Tag = U}
tagged type trait PID
which is a tagged Int type ProductId = Int with Tagged[PID]
or scalaz style type ProductId = Int @@ PID
and say other fields in the product type Name = String @@ PName
etc and a data container which contains the attributes of a product;
case class Product(pid: ProductId, name: Name, weight: Weight)
How could I write a generic extractor A => B
style method without resorting to reflection?
The reason is I want to dynamically extract a field from the Product container at runtime. I.e. the user passes in the attribute of the product they want to extract.
I.e. if I want to dynamically get ProductId
could I write a method that takes the type and returns a value e.g.
trait Extractor[A] {
def extract[B](i: A): B = //get type B from the Product class
}
Or am I over complicating things.
I could just write simple extractor class which takes A => B function and define it for each type;
trait Getter[A, B] {
def extract(i: A): B
}
//... mix this in...
trait GetPID extends Getter[Product, ProductId] {
override def extract(implicit i: Product) = i.pid
}
trait GetName extends Getter[Product, Name] {
override def extract(implicit i: Product) = i.name
}
Then add them in where needed.
val dyn = new DynamicProductExtractor with GetPID
dyn.extract
but this seems like a hassle.
I'm thing something like Lens would be useful here.
For the sake of a complete example, suppose we have the following types and some example data:
import shapeless._, tag._
trait PID; trait PName; trait PWeight
type ProductId = Int @@ PID
type Name = String @@ PName
type Weight = Double @@ PWeight
case class Product(pid: ProductId, name: Name, weight: Weight)
val pid = tag[PID](13)
val name = tag[PName]("foo")
val weight = tag[PWeight](100.0)
val product = Product(pid, name, weight)
I'm using Shapeless's tags here, but everything below would work the same with Scalaz's or your own Tagged
. Now assuming we want to be able to look up a member by type in an arbitrary case class, we can create an extractor using Shapeless's Generic
:
import ops.hlist.Selector
def extract[A] = new {
def from[C, Repr <: HList](c: C)(implicit
gen: Generic.Aux[C, Repr],
sel: Selector[Repr, A]
) = sel(gen.to(c))
}
Note that I'm using a structural type for the sake of simplicity and concision, but you could very easily define a new class that would do the same thing.
Now we can write the following:
scala> extract[ProductId].from(product)
res0: Int with shapeless.tag.Tagged[PID] = 13
Note that if the case class has more than one member with the requested type, the first will be returned. If it doesn't have any members with the right type (e.g. something like extract[Char] from(product)
), you'll get a nice compile-time error.
You could use lenses here, but you'd need to write more or less this same machinery—I don't know of a lens implementation that gives you indexing by type (Shapeless for example provides positional indices and indexing by member name).
(Note that this isn't really "dynamic", since the type that you're looking up in the case class must be statically known at compile time, but that's also the case in the examples you give above.)
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