I was wondering if anyone could provide some insight on a problem I'm having. I've made a gist with some code and explanation of my problem: https://gist.github.com/tbrown1979/9993f07c8f4fa2786c83
Basically I'm trying to make something that will allow me to convert List[String] to a case class. I've made a Reader that will allow me to do so, but I've run into the issue where a Reader defined for a case class can't contain a reader for a separate case class.
Looking at the 'non-working example' below - I encounter an issue where, when reading, I don't know how many items to pull out of the list. With Bar, which holds a Test, I would need to pull 2 elements out (because Test has two parameters). Is there a way for me to know the amount of fields a case class has just from its type? Is there a better way to do this?
Here is an example of how to use the Reader. I've included a non-working example as well.
////Working Example////
case class Foo(a: Int, s: String)
object Foo {
implicit val FooReader : Reader[Foo] =
Reader[Int :: String :: HNil].map(Generic[Foo].from _)
}
val read: ValidationNel[String, Foo] = Reader.read[Foo](List("12","text"))
println(read)//Success(Foo(12, "text"))
///////////////////////////
////Non-working Example////
case class Test(a: Int, b: String)
object Test {
implicit val TestReader: Reader[Test] =
Reader[Int :: String :: HNil].map(Generic[Test].from _)
}
case class Bar(c: Test)
object Bar {
implicit val BarReader: Reader[Bar] =
Reader[Test :: HNil].map(Generic[Bar].from _)
}
val barRead = Reader.read[Bar](List("21", "someString"))
println(barRead) //Failure(NonEmptyList("Invalid String: List()", "Exepected empty, but contained value"))
//////////////////////////
Something like this works for me (modification of this)
object ShapelessStringToTypeConverters {
import cats._, implicits._, data.ValidatedNel
import mouse._, string._, option._
import shapeless._, labelled._
private type Result[A] = ValidatedNel[ParseFailure, A]
case class ParseFailure(error: String)
trait Convert[V] {
def parse(input: String): Result[V]
}
object Convert {
def to[V](input: String)(implicit C: Convert[V]): Result[V] =
C.parse(input)
def instance[V](body: String => Result[V]): Convert[V] = new Convert[V] {
def parse(input: String): Result[V] = body(input)
}
implicit def booleans: Convert[Boolean] =
Convert.instance(
s =>
s.parseBooleanValidated
.leftMap(e => ParseFailure(s"Not a Boolean ${e.getMessage}"))
.toValidatedNel)
implicit def ints: Convert[Int] =
Convert.instance(
s =>
s.parseIntValidated
.leftMap(e => ParseFailure(s"Not an Int ${e.getMessage}"))
.toValidatedNel)
implicit def longs: Convert[Long] =
Convert.instance(
s =>
s.parseLongValidated
.leftMap(e => ParseFailure(s"Not an Long ${e.getMessage}"))
.toValidatedNel)
implicit def doubles: Convert[Double] =
Convert.instance(
s =>
s.parseDoubleValidated
.leftMap(e => ParseFailure(s"Not an Double ${e.getMessage}"))
.toValidatedNel)
implicit def strings: Convert[String] = Convert.instance(s => s.validNel)
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sealed trait SchemaMap[A] {
def readFrom(input: Map[String, String]): ValidatedNel[ParseFailure, A]
}
object SchemaMap {
def of[A](implicit s: SchemaMap[A]): SchemaMap[A] = s
private def instance[A](body: Map[String, String] => Result[A]): SchemaMap[A] = new SchemaMap[A] {
def readFrom(input: Map[String, String]): Result[A] =
body(input)
}
implicit val noOp: SchemaMap[HNil] =
SchemaMap.instance(_ => HNil.validNel)
implicit def parsing[K <: Symbol, V: Convert, T <: HList](implicit key: Witness.Aux[K], next: SchemaMap[T]): SchemaMap[FieldType[K, V] :: T] =
SchemaMap.instance { input =>
val fieldName = key.value.name
val parsedField = input
.get(fieldName)
.cata(entry => Convert.to[V](entry), ParseFailure(s"$fieldName is missing").invalidNel)
.map(f => field[K](f))
(parsedField, next.readFrom(input)).mapN(_ :: _)
}
implicit def classes[A, R <: HList](implicit repr: LabelledGeneric.Aux[A, R], schema: SchemaMap[R]): SchemaMap[A] =
SchemaMap.instance { input =>
schema.readFrom(input).map(x => repr.from(x))
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sealed trait SchemaList[A] {
def readFrom(input: List[String]): ValidatedNel[ParseFailure, A]
}
object SchemaList {
def of[A](implicit s: SchemaList[A]): SchemaList[A] = s
private def instance[A](body: List[String] => Result[A]): SchemaList[A] = new SchemaList[A] {
def readFrom(input: List[String]): Result[A] = body(input)
}
implicit val noOp: SchemaList[HNil] =
SchemaList.instance(_ => HNil.validNel)
implicit def parsing[K <: Symbol, V: Convert, T <: HList](implicit key: Witness.Aux[K], next: SchemaList[T]): SchemaList[FieldType[K, V] :: T] =
SchemaList.instance { input =>
val fieldName = key.value.name
val parsedField = input
.headOption
.cata(entry => Convert.to[V](entry), ParseFailure(s"$fieldName is missing").invalidNel)
.map(f => field[K](f))
(parsedField, next.readFrom(input.tail)).mapN(_ :: _)
}
implicit def classes[A, R <: HList](implicit repr: LabelledGeneric.Aux[A, R], schema: SchemaList[R]): SchemaList[A] =
SchemaList.instance { input =>
schema.readFrom(input).map(x => repr.from(x))
}
}
}
/*
case class Foo(a: String, b: Int, c: Boolean)
def m: Map[String, String] = Map("a" -> "hello", "c" -> "true", "b" -> "100")
def e: Map[String, String] = Map("c" -> "true", "b" -> "a100")
val result = SchemaMap.of[Foo].readFrom(m)
val lst = List("145164983", "0.01862523", "16.11681596", "21:38:57", "bid")
case class Trade0(tid: Long, price: Double, amount: Double, time: String, tpe: String)
val result2 = SchemaList.of[Trade0].readFrom(lst)
*/
Related
I have some overloaded methods that take in multiple types and return the same type:
def foo(x: Int): Foo = ...
def foo(x: String): Foo = ...
def foo(x: Boolean): Foo = ...
def foo(x: Long): Foo = ...
Now I want to define a single way to call the method, something like:
def bar(x: Int | String | Boolean | Long) = foo(x) // how to do this?
I can do it the "naive" way which I don't like very much:
def bar(x: Any) = x match {
case i:Int => foo(i)
case s:String => foo(s)
case b:Boolean => foo(b)
case l:Long => foo(l)
case _ => throw new Exception("Unsupported type")
}
Is there a better way, perhaps using Scalaz or some other library?
Try type class
trait FooDoer[T] {
def foo(x: T): Foo
}
object FooDoer {
implicit val int: FooDoer[Int] = (x: Int) => foo(x)
implicit val string: FooDoer[String] = (x: String) => foo(x)
implicit val boolean: FooDoer[Boolean] = (x: Boolean) => foo(x)
implicit val long: FooDoer[Long] = (x: Long) => foo(x)
}
def bar[T](x: T)(implicit fooDoer: FooDoer[T]): Foo = fooDoer.foo(x)
bar(1)
bar("a")
bar(true)
bar(1L)
// bar(1.0) // doesn't compile
Also sometimes the following can help
def bar[T](x: T)(implicit ev: (T =:= Int) | (T =:= String) | (T =:= Boolean) | (T =:= Long)) = ???
trait |[A, B]
trait LowPriority_| {
implicit def a[A, B](implicit a: A): A | B = null
}
object | extends LowPriority_| {
implicit def b[A, B](implicit b: B): A | B = null
}
How to define "type disjunction" (union types)?
A typeclass might work like this:
trait CanFoo[T] {
def foo(t: T): Foo
}
object CanFoo {
implicit object intFoo extends CanFoo[Int] {
def foo(i: Int) = Foo(i)
}
implicit object stringFoo extends CanFoo[String] {
def foo(s: String) = Foo(s)
}
implicit object boolFoo extends CanFoo[Boolean] {
def foo(i: Boolean) = Foo(i)
}
implicit object longFoo extends CanFoo[Long] {
def foo(i: Long) = Foo(i)
}
}
def bar[T](x: T)(implicit ev: CanFoo[T]) =
ev.foo(x)
bar(0)
bar("hello")
bar(true)
bar(0.toLong)
I would like to create a generic version of the following code:
I have a case class and an encryption function
case class Cat(name: String, age: Int, color: String)
val encrypt : String => String = _.hashCode.toString // as an example
val encryptableFields = Seq("color")
I have the Poly1 which will do the mapping in my HList
import shapeless._
import labelled._
import record._
trait enc extends Poly1 {
implicit def defaultEncrypt[K,V] = at[(K, V)] { case (k,v) =>field[K](v)}
}
object pol extends enc {
implicit def stringEncrypt[K <: Symbol] = at[(K, String)] { case (k,v) => field[K](if(encryptableFields contains k.name) encrypt(v) else v)}
}
When I'm using it it works as expected:
val cat = Cat("name", 1, "black")
val lgCat = LabelledGeneric[Cat]
val la = lgCat.to(cat)
val a = la.fields.map(pol)
lgCat.from(a)
// Cat("name", 1, "93818879")
Because it works I was thinking about creating it a generic way and encapsulate the functionality and a type class like:
trait Encryptor[T] {
val fields: Seq[String]
def encryptFields(source: T, encrypt: String => String): T
}
object Encryptor {
def forClass[A <: Product](f: Seq[String]) = new Encryptor[A] {
val fields: Seq[String] = f
override def encryptFields(source:A, encrypt: String => String): A = {
object pol extends enc {
implicit def stringEncrypt[K <: Symbol] = at[(K, String)] { case (k, v) => field[K](if (f contains k.name) encrypt(v) else v) }
}
val gen = LabelledGeneric[A]
val hList = gen.to(source)
val updated = hList.fields.map(pol)
gen.from(updated)
}
}
}
With this implementation I get the following compile time error:
Error:could not find implicit value for parameter lgen: shapeless.LabelledGeneric[A]
val gen = LabelledGeneric[A]
Tried to solve it with passing the LabelledGeneric[A] implicitly raises more questions.
def forClass[A <: Product, R <: HList](f: Seq[String])(implicit gen: implicit gen: LabelledGeneric.Aux[A, R]) = new Encryptor[A] { ... }
Complaining about Error:(46, 27) could not find implicit value for parameter fields: shapeless.ops.record.Fields[gen.Repr]; val updated = hList.fields.map(pol)
When trying to pass one:
def forClass[A <: Product, R <: HList, FOut <: HList](f: Seq[String])(
implicit gen: LabelledGeneric.Aux[A, R], fields: Fields.Aux[R, FOut])
I have the same issue.
I wonder how to overcome this issue.
I came up with another approach.
Instead of doing everything at once you can break it down to smaller pieces and operate on the HList with a different approach.
Let's create a type class for the inner representation:
trait Encryptor[T] {
def encryptFields(source: T, encrypt: String => String, fields: Seq[String]): T
}
In your example you have only Int and String fields so I'll stick to that.
import shapeless._
import labelled._
object Encryptor {
def apply[A](implicit enc: Encryptor[A]): Encryptor[A] = enc
implicit val stringEncryptor: Encryptor[String] = new Encryptor[String] {
override def encryptFields(source: String, encrypt: String => String, fields: Seq[String]) = encrypt(source)
}
implicit val intEncryptor: Encryptor[Int] = new Encryptor[Int] {
override def encryptFields(source: Int, encrypt: String => String, fields: Seq[String]) = source
}
implicit val hnilEncryptor: Encryptor[HNil] = new Encryptor[HNil] {
override def encryptFields(source: HNil, encrypt: String => String, fields: Seq[String]) = HNil
}
implicit def hlistEncryptor[A, K <: Symbol, H, T <: HList](
implicit
witness: Witness.Aux[K],
hEncryptor: Lazy[Encryptor[H]],
tEncryptor: Encryptor[T]
): Encryptor[FieldType[K, H] :: T] = new Encryptor[FieldType[K, H] :: T] {
val fieldName: String = witness.value.name
override def encryptFields(source: FieldType[K, H] :: T, encrypt: String => String, fields: Seq[String]) = {
val tail = tEncryptor.encryptFields(source.tail, encrypt, fields)
val head = if (fields contains fieldName) field[K](hEncryptor.value.encryptFields(source.head, encrypt, fields))
else source.head
head :: tail
}
}
import shapeless.LabelledGeneric
implicit def genericObjectEncryptor[A, H <: HList](
implicit
generic: LabelledGeneric.Aux[A, H],
hEncryptor: Lazy[Encryptor[H]]
): Encryptor[A] = new Encryptor[A] {
override def encryptFields(source: A, encrypt: String => String, fields: Seq[String]) = {
generic.from(hEncryptor.value.encryptFields(generic.to(source), encrypt, fields))
}
}
}
Because in your example you apply the encrypt function only on the String fields it is only used in the stringEncrytor instance. The Encryptor for the HList checks if Symbol's name of the head of HList is in the provided fields if so it applies the the encypt otherwise it skips it.
Using LabelledGeneric for making it work on any case class
To provide the same interface:
trait PayloadEncryptor[T] {
def encrypt(source: T, encrypt: String => String): T
}
object PayloadEncryptor {
def forClass[T](fieldNames: String*)(implicit encryptor: Encryptor[T]): PayloadEncryptor[T] = new PayloadEncryptor[T] {
override def encrypt(source: T, encrypt: String => String): T = {
encryptor.encryptFields(source, encrypt, fieldNames)
}
}
}
Suppose I've got partial function parf
val parf: PartialFunction[Int, String] = { case 0 => "!!!" }
Now I've got also case class A(x: Int) and I need a function to transform PartialFunction[Int, String] to PartialFunction[A, String]:
def foo(pf: PartialFunction[Int, String]): PartialFunction[A, String] = ???
For example, foo(parf) should return {case A(0) => "!!!" }. How would you write function foo ?
To maintain the correct functionality, you need to check if the inner partial function is defined on a parameter you're going to pass:
val parf: PartialFunction[Int, String] = { case 0 => "!!!" }
case class A(x: Int)
def foo(pf: PartialFunction[Int, String]): PartialFunction[A, String] = {
case A(i) if pf.isDefinedAt(i) => pf(i)
}
If you plan to do it on a larger scale, you might want to convert a partial function to an extractor object, so it can be used in pattern matches directly with a better syntax:
trait Extractor[A, B] {
def unapply(a: A): Option[B]
}
object Extractor {
implicit def partialFunctionAsExtractor[A, B](pf: PartialFunction[A, B]): Extractor[A, B] =
new Extractor[A, B] {
def unapply(a: A) = if (pf.isDefinedAt(a)) Some(pf(a)) else None
}
}
def foo2(pf: Extractor[Int, String]): PartialFunction[A, String] = {
case A(pf(str)) => str
}
foo2(parf) // implicit conversion magic
I don't see what got you confused about it? You just need to match-extract the Int out of A and then let the PF behave as it wants to behave.
scala> case class A(x: Int)
// defined class A
scala> val parf: PartialFunction[Int, String] = { case 0 => "!!!" }
// parf: PartialFunction[Int,String] = <function1>
scala> def foo(pf: PartialFunction[Int, String]): PartialFunction[A, String] = {
| case A(x) if pf.isDefinedAt(x) => pf(x)
| }
// foo: (pf: PartialFunction[Int,String])PartialFunction[A,String]
scala> val parfA = foo(parf)
// parfA: PartialFunction[A,String] = <function1>
scala> parfA(A(0))
//res0: String = !!!
scala> parfA(A(1))
// scala.MatchError: A(1) (of class A)
// at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:254)
// at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:252)
// at $anonfun$1.applyOrElse(<console>:11)
// at $anonfun$1.applyOrElse(<console>:11)
// at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:34)
// at $anonfun$foo$1.applyOrElse(<console>:13)
// at $anonfun$foo$1.applyOrElse(<console>:13)
// at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:34)
// ... 28 elided
#Oleg Pyzhcov already provided a great solution. Another approach would be to create a PartialFunction[A, Int] that is defined at A(0), and use andThen to chain it with parf:
val parf: PartialFunction[Int, String] = { case 0 => "!!!" }
case class A(n: Int)
val bar: PartialFunction[A, Int] = { case a: A if a.n == 0 => a.n }
def foo(pf: PartialFunction[Int, String]): PartialFunction[A, String] =
bar andThen pf
// foo: (pf: PartialFunction[Int,String])PartialFunction[A,String]
foo(parf)
// res1: PartialFunction[A,String] = <function1>
do you think what i did make sense? Is there a better way do encode a case class into Item? e.g. i'm not happy with ignoring in some cases an input param!
import shapeless.labelled.FieldType
import shapeless.{::, DepFn2, HList, HNil, LabelledGeneric, Witness}
import scala.collection.mutable
// mock of sdk item
class Item(val map: mutable.Map[String, Any] = mutable.Map[String, Any]()) {
def getString(attrName: String): String = map.get(attrName).get.asInstanceOf[String]
def getInt(attrName: String): Int = map.get(attrName).get.asInstanceOf[Int]
def getBoolean(attrName: String): Boolean = map.get(attrName).get.asInstanceOf[Boolean]
// def getMap(attrName: String): Map[String, String] = Map("attrName" -> "attrValue")
def setString(attrName: String, value: String): Unit = map.put(attrName, value)
def setInt(attrName: String, value: Int): Unit = map.put(attrName, value)
def setBoolean(attrName: String, value: Boolean): Unit = map.put(attrName, value)
override def toString() = map.toString()
}
trait ItemEncoder[A] extends DepFn2[String, A] {
type Out = Item
}
object ItemEncoder {
def apply[A](implicit encoder: ItemEncoder[A]): ItemEncoder[A] = encoder
def instance[A](f: (String, A) => Item): ItemEncoder[A] =
new ItemEncoder[A] {
override def apply(attrName: String, value: A): Out = f(attrName, value)
}
}
implicit val stringEncoder: ItemEncoder[String] =
ItemEncoder.instance { (attrName, value) =>
val item = new Item()
item.setString(attrName, value)
item
}
implicit val intEncoder: ItemEncoder[Int] =
ItemEncoder.instance { (attrName, value) =>
val item = new Item()
item.setInt(attrName, value)
item
}
implicit val booleanEncoder: ItemEncoder[Boolean] =
ItemEncoder.instance { (attrName, value) =>
val item = new Item()
item.setBoolean(attrName, value)
item
}
implicit val hnilEncoder: ItemEncoder[HNil] =
ItemEncoder.instance((attrName, value) => new Item())
def merge(i1: Item, i2: Item): Item = new Item(i1.map ++ i2.map)
implicit def hlistEncoder[K <: Symbol, L, H, T <: HList](
implicit
witness: Witness.Aux[K],
hEncoder: ItemEncoder[H],
tEncoder: ItemEncoder[T]
): ItemEncoder[FieldType[K, H] :: T] = {
ItemEncoder.instance { (_, value) =>
val attrName = witness.value.name
merge(hEncoder.apply(attrName, value.head), tEncoder.apply(attrName, value.tail))
}
}
implicit def genericEncoder[A, R](
implicit
generic: LabelledGeneric.Aux[A, R],
itemEncoder: ItemEncoder[R]
): ItemEncoder[A] =
ItemEncoder.instance { (attrName, value) =>
itemEncoder.apply(attrName, generic.to(value))
}
case class Person(name: String, age: Int, married: Boolean, manager: Boolean)
case class IceCream(name: String, subName: String, price: Int)
val genericPerson = LabelledGeneric[Person].to(Person("bob", 37, true, true))
def encode[A](toEncode: A)(implicit itemEncoder: ItemEncoder[A]) =
itemEncoder("", toEncode)
Maybe it will be better to use ToMap or something like this, and the convert it to Item
After getting deeper into the topic i managed to implement ItemEncoder that converts a case class with arbitrary nesting into Item like this:
import com.amazonaws.services.dynamodbv2.document.Item
import shapeless.labelled.FieldType
import shapeless.{::, HList, HNil, LabelledGeneric, Witness, _}
trait ItemEncoder[A] {
def encode(value: A): Item
}
object ItemEncoder {
def apply[A](implicit encoder: ItemEncoder[A]): ItemEncoder[A] = encoder
def instance[A](f: A => Item): ItemEncoder[A] =
new ItemEncoder[A] {
override def encode(value: A): Item = f(value)
}
implicit def stringEncoder[K <: Symbol, V <: String](
implicit witness: Witness.Aux[K]
): ItemEncoder[FieldType[K, V]] =
instance { value =>
val item = new Item
item.withString(witness.value.name, value)
item
}
implicit def intEncoder[K <: Symbol, V <: Int](
implicit witness: Witness.Aux[K]
): ItemEncoder[FieldType[K, V]] =
instance { value =>
val item = new Item
item.withInt(witness.value.name, value)
item
}
implicit def booleanEncoder[K <: Symbol, V <: Boolean](
implicit witness: Witness.Aux[K]
): ItemEncoder[FieldType[K, V]] =
instance { value =>
val item = new Item
item.withBoolean(witness.value.name, value)
item
}
// K is key, A is value, R is HList representation of A
implicit def nestedClassEncoder[K <: Symbol, A, R](
implicit
witness: Witness.Aux[K],
generic: LabelledGeneric.Aux[A, R],
encoder: ItemEncoder[R]
): ItemEncoder[FieldType[K, A]] =
instance { value =>
val i = encoder.encode(generic.to(value))
val item = new Item
val m = new java.util.HashMap[String, Any]()
item.withMap(witness.value.name, i.asMap())
item
}
import cats.Monoid
implicit val itemMonoid: Monoid[Item] = new Monoid[Item] {
override def empty: Item = new Item()
override def combine(x: Item, y: Item): Item = {
val m = x.asMap
m.putAll(y.asMap())
Item.fromMap(m)
}
}
implicit val hnilEncoder: ItemEncoder[HNil] =
instance(_ => new Item())
implicit def hlistEncoder[H, T <: HList](
implicit
hEncoder: Lazy[ItemEncoder[H]],
tEncoder: ItemEncoder[T],
monoid: Monoid[Item]
): ItemEncoder[H :: T] =
instance { value =>
// println("hlist enc")
val itemX = hEncoder.value.encode(value.head)
val itemY = tEncoder.encode(value.tail)
monoid.combine(itemX, itemY)
}
implicit def genericEncoder[A, R](
implicit
generic: LabelledGeneric.Aux[A, R],
itemEncoder: Lazy[ItemEncoder[R]]
): ItemEncoder[A] =
instance { value =>
// println("gen enc")
itemEncoder.value.encode(generic.to(value))
}
def encode[A](toEncode: A)(implicit itemEncoder: ItemEncoder[A]) =
itemEncoder.encode(toEncode)
}
Current implementation is a bit simplified. So it contains ItemEncoder implementation only for such a primitive types as String, Int and Boolean. But other primitive types can be easily added by using the present ones as example.
You can find complete implementation with QuickCheck tests on Git
The following code succeeds, but is there a better way of doing the same thing? Perhaps something specific to case classes? In the following code, for each field of type String in my simple case class, the code goes through my list of instances of that case class and finds the length of the longest string of that field.
case class CrmContractorRow(
id: Long,
bankCharges: String,
overTime: String,
name$id: Long,
mgmtFee: String,
contractDetails$id: Long,
email: String,
copyOfVisa: String)
object Go {
def main(args: Array[String]) {
val a = CrmContractorRow(1,"1","1",4444,"1",1,"1","1")
val b = CrmContractorRow(22,"22","22",22,"55555",22,"nine long","22")
val c = CrmContractorRow(333,"333","333",333,"333",333,"333","333")
val rows = List(a,b,c)
c.getClass.getDeclaredFields.filter(p => p.getType == classOf[String]).foreach{f =>
f.setAccessible(true)
println(f.getName + ": " + rows.map(row => f.get(row).asInstanceOf[String]).maxBy(_.length))
}
}
}
Result:
bankCharges: 3
overTime: 3
mgmtFee: 5
email: 9
copyOfVisa: 3
If you want to do this kind of thing with Shapeless, I'd strongly suggest defining a custom type class that handles the complicated part and allows you to keep that stuff separate from the rest of your logic.
In this case it sounds like the tricky part of what you're specifically trying to do is getting the mapping from field names to string lengths for all of the String members of a case class. Here's a type class that does that:
import shapeless._, shapeless.labelled.FieldType
trait StringFieldLengths[A] { def apply(a: A): Map[String, Int] }
object StringFieldLengths extends LowPriorityStringFieldLengths {
implicit val hnilInstance: StringFieldLengths[HNil] =
new StringFieldLengths[HNil] {
def apply(a: HNil): Map[String, Int] = Map.empty
}
implicit def caseClassInstance[A, R <: HList](implicit
gen: LabelledGeneric.Aux[A, R],
sfl: StringFieldLengths[R]
): StringFieldLengths[A] = new StringFieldLengths[A] {
def apply(a: A): Map[String, Int] = sfl(gen.to(a))
}
implicit def hconsStringInstance[K <: Symbol, T <: HList](implicit
sfl: StringFieldLengths[T],
key: Witness.Aux[K]
): StringFieldLengths[FieldType[K, String] :: T] =
new StringFieldLengths[FieldType[K, String] :: T] {
def apply(a: FieldType[K, String] :: T): Map[String, Int] =
sfl(a.tail).updated(key.value.name, a.head.length)
}
}
sealed class LowPriorityStringFieldLengths {
implicit def hconsInstance[K, V, T <: HList](implicit
sfl: StringFieldLengths[T]
): StringFieldLengths[FieldType[K, V] :: T] =
new StringFieldLengths[FieldType[K, V] :: T] {
def apply(a: FieldType[K, V] :: T): Map[String, Int] = sfl(a.tail)
}
}
This looks complex, but once you start working with Shapeless a bit you learn to write this kind of thing in your sleep.
Now you can write the logic of your operation in a relatively straightforward way:
def maxStringLengths[A: StringFieldLengths](as: List[A]): Map[String, Int] =
as.map(implicitly[StringFieldLengths[A]].apply).foldLeft(
Map.empty[String, Int]
) {
case (x, y) => x.foldLeft(y) {
case (acc, (k, v)) =>
acc.updated(k, acc.get(k).fold(v)(accV => math.max(accV, v)))
}
}
And then (given rows as defined in the question):
scala> maxStringLengths(rows).foreach(println)
(bankCharges,3)
(overTime,3)
(mgmtFee,5)
(email,9)
(copyOfVisa,3)
This will work for absolutely any case class.
If this is a one-off thing, you might as well use runtime reflection, or you could use the Poly1 approach in Giovanni Caporaletti's answer—it's less generic and it mixes up the different parts of the solution in a way I don't prefer, but it should work just fine. If this is something you're doing a lot of, though, I'd suggest the approach I've given here.
If you want to use shapeless to get the string fields of a case class and avoid reflection you can do something like this:
import shapeless._
import labelled._
trait lowerPriorityfilterStrings extends Poly2 {
implicit def default[A] = at[Vector[(String, String)], A] { case (acc, _) => acc }
}
object filterStrings extends lowerPriorityfilterStrings {
implicit def caseString[K <: Symbol](implicit w: Witness.Aux[K]) = at[Vector[(String, String)], FieldType[K, String]] {
case (acc, x) => acc :+ (w.value.name -> x)
}
}
val gen = LabelledGeneric[CrmContractorRow]
val a = CrmContractorRow(1,"1","1",4444,"1",1,"1","1")
val b = CrmContractorRow(22,"22","22",22,"55555",22,"nine long","22")
val c = CrmContractorRow(333,"333","333",333,"333",333,"333","333")
val rows = List(a,b,c)
val result = rows
// get for each element a Vector of (fieldName -> stringField) pairs for the string fields
.map(r => gen.to(r).foldLeft(Vector[(String, String)]())(filterStrings))
// get the maximum for each "column"
.reduceLeft((best, row) => best.zip(row).map {
case (kv1#(_, v1), (_, v2)) if v1.length > v2.length => kv1
case (_, kv2) => kv2
})
result foreach { case (k, v) => println(s"$k: $v") }
You probably want to use Scala reflection:
import scala.reflect.runtime.universe._
val rm = runtimeMirror(getClass.getClassLoader)
val instanceMirrors = rows map rm.reflect
typeOf[CrmContractorRow].members collect {
case m: MethodSymbol if m.isCaseAccessor && m.returnType =:= typeOf[String] =>
val maxValue = instanceMirrors map (_.reflectField(m).get.asInstanceOf[String]) maxBy (_.length)
println(s"${m.name}: $maxValue")
}
So that you can avoid issues with cases like:
case class CrmContractorRow(id: Long, bankCharges: String, overTime: String, name$id: Long, mgmtFee: String, contractDetails$id: Long, email: String, copyOfVisa: String) {
val unwantedVal = "jdjd"
}
Cheers
I have refactored your code to something more reuseable:
import scala.reflect.ClassTag
case class CrmContractorRow(
id: Long,
bankCharges: String,
overTime: String,
name$id: Long,
mgmtFee: String,
contractDetails$id: Long,
email: String,
copyOfVisa: String)
object Go{
def main(args: Array[String]) {
val a = CrmContractorRow(1,"1","1",4444,"1",1,"1","1")
val b = CrmContractorRow(22,"22","22",22,"55555",22,"nine long","22")
val c = CrmContractorRow(333,"333","333",333,"333",333,"333","333")
val rows = List(a,b,c)
val initEmptyColumns = List.fill(a.productArity)(List())
def aggregateColumns[Tin:ClassTag,Tagg](rows: Iterable[Product], aggregate: Iterable[Tin] => Tagg) = {
val columnsWithMatchingType = (0 until rows.head.productArity).filter {
index => rows.head.productElement(index) match {case t: Tin => true; case _ => false}
}
def columnIterable(col: Int) = rows.map(_.productElement(col)).asInstanceOf[Iterable[Tin]]
columnsWithMatchingType.map(index => (index,aggregate(columnIterable(index))))
}
def extractCaseClassFieldNames[T: scala.reflect.ClassTag] = {
scala.reflect.classTag[T].runtimeClass.getDeclaredFields.filter(!_.isSynthetic).map(_.getName)
}
val agg = aggregateColumns[String,String] (rows,_.maxBy(_.length))
val fieldNames = extractCaseClassFieldNames[CrmContractorRow]
agg.map{case (index,value) => fieldNames(index) + ": "+ value}.foreach(println)
}
}
Using shapeless would get rid of the .asInstanceOf, but the essence would be the same. The main problem with the given code was that it was not re-usable since the aggregation logic was mixed with the reflection logic to get the field names.