Suppose I have an item of the type (Option[Long], Option[Long], Option[Long], Option[Long], Option[Long]), and I want to convert it to an item of type (Long, Long, Long, Long, Long). I want each coordinate to contain the value of the option (if the option contains a "Some" value), or be zero otherwise.
Usually if I have an item of type Option[Long], I'd do something like
item match {
case Some(n) => n
case None => 0
}
But I can't do that with a 5 coordinate item unless I want to list out all 32 possibilities. What can I do instead?
Simple solution:
item match {
case (a, b, c, d, e) => (a.getOrElse(0), b.getOrElse(0), c.getOrElse(0), d.getOrElse(0), e.getOrElse(0))
}
Obviously this isn't very generic. For that you'll probably want to look at Shapeless but I'll leave that answer to the resident experts. ;)
Using Shapeless you could do:
import shapeless._
import syntax.std.tuple._
import poly._
object defaultValue extends Poly1 {
implicit def defaultOptionLong = at[Option[Long]](_.getOrElse(0L))
}
val tuple : (Option[Long], Option[Long], Option[Long], Option[Long], Option[Long]) =
(Some(1L), None, Some(3L), Some(4L), None)
tuple.map(defaultValue)
// (Long, Long, Long, Long, Long) = (1,0,3,4,0)
You need to explicitly specify type Option[Int] if you don't use Option.apply (see this question).
(Option(1L), Option(2L)).map(defaultValue)
// (Long, Long) = (1,2)
(Some(3L), Some(4L)).map(defaulValue) // does not compile
val t : (Option[Long], Option[Long]) = (Some(3L), Some(4L))
t.map(defaultValue)
// (Long, Long) = (3,4)
(Option(5), None).map(defaultValue) // does not compile
val t2 (Option[Long], Option[Long]) = (Option(5), None)
t2.map(defaultValue)
// (Long, Long) = (5,0)
We could also provide default values for other types:
object defaultValue extends Poly1 {
implicit def caseLong = at[Option[Long]](_.getOrElse(0L))
implicit def caseInt = at[Option[Int]](_.getOrElse(0))
implicit def caseString = at[Option[String]](_.getOrElse("scala"))
}
val tuple2 : (Option[Int], Option[Long], Option[String]) = (None, None, None)
tuple2.map(defaultValue)
// (Int, Long, String) = (0,0,scala)
Edit: The problem with the need of explicit declaration of Some(5L) as Option[Long] can be solved using generics in the poly function :
objec defaultValue extends Poly1 {
implicit def caseLong[L <: Option[Long]] = at[L](_.getOrElse(0L))
implicit def caseInt[I <: Option[Int]] = at[I](_.getOrElse(0))
implicit def caseString[S <: Option[String]] = at[S](_.getOrElse("scala"))
}
(Some("A"), Some(1), None: Option[Int], None: Option[String]).map(defaultValue)
// (String, Int, Int, String) = (A,1,0,scala)
You can simply do:
val res = for {
a <- item._1.orElse(0L)
b <- item._2.orElse(0L)
c <- item._3.orElse(0L)
d <- item._4.orElse(0L)
e <- item._5.orElse(0L)
} yield (a, b, c, d, e)
Not the nicest but easy to implement and understand.
Another possible solution:
item.productIterator.collect{
case Some(a: Int) => a
case _ => 0
}.toList match {
case List(a,b,c,d,e) => (a,b,c,d,e)
case _ => (0,0,0,0,0) //or throw exception depending on your logic
}
Related
I have a set of model classes and a subset of those models have 2 properties called createdBy and modifiedBy.
I need to populate those attributes only for those objects that has those attributes. Currently I'm doing it in a pattern match with some boilerplate code.
case class DataSourceInstanceRow(id: Int, value: String, createdBy: Option[String], modifiedBy: Option[String])
case class FormDefinitionRow(id: Int, formData: String, createdBy: Option[String], modifiedBy: Option[String])
case class DecisionTableDefinitionRow(id: Int, rows: Int, definitions: List[String], createdBy: Option[String], modifiedBy: Option[String])
case class ReportDef(id: Int, reportType: Int, reportName: String)
def populateLogs[T](t: T, user: String): T = {
t match {
case ds: DataSourceInstanceRow =>
if(ds.id == -1) ds.copy(modifiedBy = Some(user), createdBy = Some(user)).asInstanceOf[T]
else ds.copy(modifiedBy = Some(user)).asInstanceOf[T]
case fd: FormDefinitionRow =>
if(fd.id == -1) fd.copy(modifiedBy = Some(user), createdBy = Some(user)).asInstanceOf[T]
else fd.copy(modifiedBy = Some(user)).asInstanceOf[T]
case dtd: DecisionTableDefinitionRow =>
if(dtd.id == -1) dtd.copy(modifiedBy = Some(user), createdBy = Some(user)).asInstanceOf[T]
else dtd.copy(modifiedBy = Some(user)).asInstanceOf[T]
case o => o
}
}
DataSourceInstanceRow, FormDefinitionRow, DecisiontableDefinitionRow have modifiedBy and createdBy properties. But not ReportDef
How can I use shapeless to create an abstraction to remove the boilerplate from above pattern matching?
You can do this kind of thing with Shapeless's Updater:
import shapeless.{ LabelledGeneric, HList, Witness }
import shapeless.labelled.{FieldType, field}
import shapeless.ops.record.Updater
type CreatedBy = Witness.`'createdBy`.T
type ModifiedBy = Witness.`'modifiedBy`.T
def populateLogs[T, R <: HList](t: T, user: String)(implicit
gen: LabelledGeneric.Aux[T, R],
cb: Updater.Aux[R, FieldType[CreatedBy, Option[String]], R] = null,
mb: Updater.Aux[R, FieldType[ModifiedBy, Option[String]], R] = null
): T = (
for {
createdBy <- Option(cb)
modifiedBy <- Option(mb)
} yield gen.from(
createdBy(modifiedBy(gen.to(t), field(Some(user))), field(Some(user)))
)
).getOrElse(t)
And then:
scala> populateLogs(DataSourceInstanceRow(1, "abc", None, None), "foo")
res0: DataSourceInstanceRow = DataSourceInstanceRow(1,abc,Some(foo),Some(foo))
scala> populateLogs(ReportDef(1, 2, "abc"), "foo")
res1: ReportDef = ReportDef(1,2,abc)
This implementation uses a trick based on the fact that you can put a null default value on an implicit parameter and the compiler will use that if it can't find an implicit. It's simple and works just fine, but some people hate it. A more principled approach uses implicit prioritization:
trait UpdateBoth[T] extends ((T, String) => T)
object UpdateBoth extends LowPriorityUpdateBothInstances {
implicit def updateWithFields[T, R <: HList](implicit
gen: LabelledGeneric.Aux[T, R],
cb: Updater.Aux[R, FieldType[CreatedBy, Option[String]], R],
mb: Updater.Aux[R, FieldType[ModifiedBy, Option[String]], R]
): UpdateBoth[T] = (t, user) =>
gen.from(cb(mb(gen.to(t), field(Some(user))), field(Some(user))))
}
trait LowPriorityUpdateBothInstances {
implicit def updateAny[T]: UpdateBoth[T] = (t, _) => t
}
def populateLogs[T](t: T, user: String)(implicit update: UpdateBoth[T]): T =
update(t, user)
That will work exactly the same way.
I have a class like
case class A(a: Int, b: String)
and a function
def f(a: Int)(implicit b: String) =???
Is it possible to do something like this?
val a = A(11, "hello")
a match {
case A(a, implicit b) => f(a)
}
How can I make the parameter b implicit without explicitly declaring it after extraction.
I wouldn't worry about passing the argument implicitly, since you can easily provide it explicitly in this particular case:
case class A(a: Int, b: String)
def f(a: Int)(implicit b: String) =???
val a = A(11, "hello")
a match {
case A(a, b) => f(a)(b)
}
If you must pass the value implicitly, it needs to be declared in scope. For example:
a match {
case A(a, b) => {
implicit val s = b
f(a)
}
}
Also, as has been pointed out, don't use implicit with a common type. It's better if you wrap it in another class:
case class A(a: Int, b: String)
case class SomeCustomString(s: String)
def f(a: Int)(implicit b: SomeCustomString) =???
val a = A(11, "hello")
a match {
case A(a, b) => {
implicit val s = SomeCustomString(b)
f(a)
}
}
If you could explain the use case for the implicit argument, I could provide a better example.
Update: There is a kind of way to do what you want:
case class SomeCustomString(s: String)
case class A(a: Int, b: String) {
implicit val s = SomeCustomString(b)
}
def f(a: Int)(implicit s: SomeCustomString) =???
val a = A(11, "hello")
import a._
f(a.a)
Or, if you must have it within a pattern match, that last bit would be:
a match {
case x: A => {
import x._
f(x.a)
}
}
Update 2: Or, as yet another approach (again, with implicit largely redundant):
case class SomeCustomString(s: String)
case class A(a: Int, b: String) {
implicit val s = SomeCustomString(b)
def invokeF = f(a)
}
def f(a: Int)(implicit s: SomeCustomString) =???
val a = A(11, "hello")
a.invokeF
or
a match {
case x: A => x.invokeF
}
Does that help?
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.
I want to map a pair of options of String like the following
val pair: (Option[String], Option[String]) = (Some("a"), None)
val mapped: (String, String) = pair map {case (a:Option[String],b:Option[String]) => (a.getOrElse(""),b.getOrElse(""))}
but the output signature is different from what I expected
(Option[String],(String,String))
It seems that I'm missing something here... maybe scalaz or shapeless allows for such functionality of mapping tuples?
Simple change from map to match you'll get expected types.
scala> val pair: (Option[String], Option[String]) = (Some("a"), None)
pair: (Option[String], Option[String]) = (Some(a),None)
scala>
scala> val mapped: (String, String) = pair match {case (a:Option[String],b:Option[String]) => (a.getOrElse(""),b.getOrElse(""))}
mapped: (String, String) = (a,"")
scala>
scala> mapped
res8: (String, String) = (a,"")
In case if you specifically want to do things like that with shapeless, you should make some preparations.
First tell your compiler what you want to use in case of None:
class Default[T](val value: T)
implicit object defaultString extends Default[String]("")
Now create your function to map:
import shapeless._
object extract extends Poly1 {
implicit def withDefault[T](implicit default: Default[T]) =
at[Option[T]](_ getOrElse default.value)
}
Now use shapeless extension for tuples:
import syntax.std.tuple._
pair.map(extract) // res0: (String, String) = (a,)
I seek succinct code to initialize simple Scala case classes from Strings (e.g. a csv line):
case class Person(name: String, age: Double)
case class Book(title: String, author: String, year: Int)
case class Country(name: String, population: Int, area: Double)
val amy = Creator.create[Person]("Amy,54.2")
val fred = Creator.create[Person]("Fred,23")
val hamlet = Creator.create[Book]("Hamlet,Shakespeare,1600")
val finland = Creator.create[Country]("Finland,4500000,338424")
What's the simplest Creator object to do this? I would learn a lot about Scala from seeing a good solution to this.
(Note that companion objects Person, Book and Country should not to be forced to exist. That would be boiler-plate!)
I'm going to give a solution that's about as simple as you can get given some reasonable constraints about type safety (no runtime exceptions, no runtime reflection, etc.), using Shapeless for generic derivation:
import scala.util.Try
import shapeless._
trait Creator[A] { def apply(s: String): Option[A] }
object Creator {
def create[A](s: String)(implicit c: Creator[A]): Option[A] = c(s)
def instance[A](parse: String => Option[A]): Creator[A] = new Creator[A] {
def apply(s: String): Option[A] = parse(s)
}
implicit val stringCreate: Creator[String] = instance(Some(_))
implicit val intCreate: Creator[Int] = instance(s => Try(s.toInt).toOption)
implicit val doubleCreate: Creator[Double] =
instance(s => Try(s.toDouble).toOption)
implicit val hnilCreator: Creator[HNil] =
instance(s => if (s.isEmpty) Some(HNil) else None)
private[this] val NextCell = "^([^,]+)(?:,(.+))?$".r
implicit def hconsCreate[H: Creator, T <: HList: Creator]: Creator[H :: T] =
instance {
case NextCell(cell, rest) => for {
h <- create[H](cell)
t <- create[T](Option(rest).getOrElse(""))
} yield h :: t
case _ => None
}
implicit def caseClassCreate[C, R <: HList](implicit
gen: Generic.Aux[C, R],
rc: Creator[R]
): Creator[C] = instance(s => rc(s).map(gen.from))
}
This work exactly as specified (although note that the values are wrapped in Option to represent the fact that the parsing operation can fail):
scala> case class Person(name: String, age: Double)
defined class Person
scala> case class Book(title: String, author: String, year: Int)
defined class Book
scala> case class Country(name: String, population: Int, area: Double)
defined class Country
scala> val amy = Creator.create[Person]("Amy,54.2")
amy: Option[Person] = Some(Person(Amy,54.2))
scala> val fred = Creator.create[Person]("Fred,23")
fred: Option[Person] = Some(Person(Fred,23.0))
scala> val hamlet = Creator.create[Book]("Hamlet,Shakespeare,1600")
hamlet: Option[Book] = Some(Book(Hamlet,Shakespeare,1600))
scala> val finland = Creator.create[Country]("Finland,4500000,338424")
finland: Option[Country] = Some(Country(Finland,4500000,338424.0))
Creator here is a type class that provides evidence that we can parse a string into a given type. We have to provide explicit instances for basic types like String, Int, etc., but we can use Shapeless to generically derive instances for case classes (assuming that we have Creator instances for all of their member types).
object Creator {
def create[T: ClassTag](params: String): T = {
val ctor = implicitly[ClassTag[T]].runtimeClass.getConstructors.head
val types = ctor.getParameterTypes
val paramsArray = params.split(",").map(_.trim)
val paramsWithTypes = paramsArray zip types
val parameters = paramsWithTypes.map {
case (param, clas) =>
clas.getName match {
case "int" => param.toInt.asInstanceOf[Object] // needed only for AnyVal types
case "double" => param.toDouble.asInstanceOf[Object] // needed only for AnyVal types
case _ =>
val paramConstructor = clas.getConstructor(param.getClass)
paramConstructor.newInstance(param).asInstanceOf[Object]
}
}
val r = ctor.newInstance(parameters: _*)
r.asInstanceOf[T]
}
}