Mapping an ADT to multiple columns in Slick - scala

I'm trying to map an ADT (case classes inheriting from a sealed trait) as multiple columns of the same table, using Slick, something like:
sealed trait OrValue
case class IntValue(value: Int)
case class StringValue(value: String)
case class Thing(id: Int, value: OrValue)
class Things(tag: Tag) extends Table[Thing](tag, "things") {
def id = column[Int]("id", O.PrimaryKey)
def intValue = column[Option[Int]]("intValue")
def stringValue = column[Option[String]]("stringValue")
def toThing(_id: String, _intValue: Option[Int], _stringValue: Option[String]): Thing = Thing(id, ((_intValue, _stringValue) match {
case (Some(a), None) => IntValue(a)
case (None, Some(a)) => StringValue(a)
}
def fromThing(t: Thing): (String, Option[Int], Option[String]) = ??? // elided
def * = (id, intValue, stringValue) <> ((toThing _).tupled, fromThing)
}
This is not compling:
[error] found : [U]slick.lifted.MappedProjection[Thing,U]
[error] required: slick.lifted.ProvenShape[Thing]
[error] ) <> ((toThing _).tupled, fromThing _)
Am I approaching this the wrong way?
What's the idiomatic way of representing an ADT?

The problem is in signature of methods toThing and fromThing.
It should look so:
sealed trait OrValue
case class IntValue(value: Int)
case class StringValue(value: String)
case class Thing(id: Int, value: OrValue)
class Things(tag: Tag) extends Table[Thing](tag, "things") {
def id = column[Int]("id", O.PrimaryKey)
def intValue = column[Option[Int]]("intValue")
def stringValue = column[Option[String]]("stringValue")
def toThing(source: (Int, Option[Int], Option[String])): Thing = source match {
case (id, intValue, stringValue) => ??? //TODO implement
}
def fromThing(t: Thing): Option[(Int, Option[Int], Option[String])] = ??? //TODO implement
def * = (id, intValue, stringValue) <> (toThing _, fromThing _)
}

You shouldn't name mapping and case classes with the same name. Change map class to Things, for example.

Related

Scala error when providing subclass instance in place of superclass?

I am just trying things out in scala and I wrote this code
object Main:
def main(args: Array[String]): Unit =
val dInt: Data[Int] = IntData(1)
val dString: Data[String] = StringData("hello")
val Data(deconstructedInt) = dInt // unapply
val Data(deconstructedString) = dString // unapply
println(deconstructedInt)
println(deconstructedString)
sealed trait Data[+T]:
def get: T
case class IntData(get: Int) extends Data[Int]
case class StringData(get: String) extends Data[String]
object Data:
def apply[T](input: T): Data[T] = input match {
case i: Int => IntData(i) //compile error here
case s: String => StringData(s) //compile error here
}
def unapply[T](d: Data[T]): Option[String] = d match {
case IntData(get) => Some(s"int data => get = $get")
case StringData(get) => Some(s"string data => get = $get")
}
I get at the location commented in the code this error
Found: IntData
Required: Data[T]
case i: Int => IntData(i)
why I am getting this error, isn't IntData (or StringData) a subclass of Data ?
IntData is a subtype of Data[Int]. So if T is not Int, IntData is not a subtype of Data[T]. Now, you might say, if input matches the first case, then clearly T is Int. But the compiler is not smart to realise this!
You could try using Scala 3's new match types:
type DataOf[T] = T match {
case Int => IntData
case String => StringData
}
def apply[T](input: T): DataOf[T] = input match {
case i: Int => IntData(i)
case s: String => StringData(s)
}
Another alternative is union types:
def apply(input: Int | String): Data[Int | String] = input match {
case i: Int => IntData(i)
case s: String => StringData(s)
}
However, you will lose type information in doing this. Using the match types solution, apply(42) is inferred to have type IntData. Using the union types solution, it is inferred to have type Data[Int | String].
This way compiler connects the dots between Ts and it works:
object Main:
def main(args: Array[String]): Unit =
val dInt: Data[Int] = IntData(1)
val dString: Data[String] = StringData("hello")
val Data(deconstructedInt) = dInt // unapply
val Data(deconstructedString) = dString // unapply
println(deconstructedInt)
println(deconstructedString)
sealed trait Data[+T]:
def get: T
case class IntData[T <: Int](get: T) extends Data[T]
case class StringData[T <: String](get: T) extends Data[T]
object Data:
def apply[T](input: T): Data[T] = input match {
case i: Int => IntData(i)
case s: String => StringData(s)
}
def unapply[T](d: Data[T]): Option[String] = d match {
case IntData(get) => Some(s"int data => get = $get")
case StringData(get) => Some(s"string data => get = $get")
}

compare case class fields with sub fields of another case class in scala

I have the following 3 case classes:
case class Profile(name: String,
age: Int,
bankInfoData: BankInfoData,
userUpdatedFields: Option[UserUpdatedFields])
case class BankInfoData(accountNumber: Int,
bankAddress: String,
bankNumber: Int,
contactPerson: String,
phoneNumber: Int,
accountType: AccountType)
case class UserUpdatedFields(contactPerson: String,
phoneNumber: Int,
accountType: AccountType)
this is just enums, but i added anyway:
sealed trait AccountType extends EnumEntry
object AccountType extends Enum[AccountType] {
val values: IndexedSeq[AccountType] = findValues
case object Personal extends AccountType
case object Business extends AccountType
}
my task is - i need to write a funcc Profile and compare UserUpdatedFields(all of the fields) with SOME of the fields in BankInfoData...this func is to find which fields where updated.
so I wrote this func:
def findDiff(profile: Profile): Seq[String] = {
var listOfFieldsThatChanged: List[String] = List.empty
if (profile.bankInfoData.contactPerson != profile.userUpdatedFields.get.contactPerson){
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "contactPerson"
}
if (profile.bankInfoData.phoneNumber != profile.userUpdatedFields.get.phoneNumber) {
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "phoneNumber"
}
if (profile.bankInfoData.accountType != profile.userUpdatedFields.get.accountType) {
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "accountType"
}
listOfFieldsThatChanged
}
val profile =
Profile(
"nir",
34,
BankInfoData(1, "somewhere", 2, "john", 123, AccountType.Personal),
Some(UserUpdatedFields("lee", 321, AccountType.Personal))
)
findDiff(profile)
it works, but wanted something cleaner..any suggestions?
Each case class extends Product interface so we could use it to convert case classes into sets of (field, value) elements. Then we can use set operations to find the difference. For example,
def findDiff(profile: Profile): Seq[String] = {
val userUpdatedFields = profile.userUpdatedFields.get
val bankInfoData = profile.bankInfoData
val updatedFieldsMap = userUpdatedFields.productElementNames.zip(userUpdatedFields.productIterator).toMap
val bankInfoDataMap = bankInfoData.productElementNames.zip(bankInfoData.productIterator).toMap
val bankInfoDataSubsetMap = bankInfoDataMap.view.filterKeys(userUpdatedFieldsMap.keys.toList.contains)
(bankInfoDataSubsetMap.toSet diff updatedFieldsMap.toSet).toList.map { case (field, value) => field }
}
Now findDiff(profile) should output List(phoneNumber, contactPerson). Note we are using productElementNames from Scala 2.13 to get the filed names which we then zip with corresponding values
userUpdatedFields.productElementNames.zip(userUpdatedFields.productIterator)
Also we rely on filterKeys and diff.
A simple improvement would be to introduce a trait
trait Fields {
val contactPerson: String
val phoneNumber: Int
val accountType: AccountType
def findDiff(that: Fields): Seq[String] = Seq(
Some(contactPerson).filter(_ != that.contactPerson).map(_ => "contactPerson"),
Some(phoneNumber).filter(_ != that.phoneNumber).map(_ => "phoneNumber"),
Some(accountType).filter(_ != that.accountType).map(_ => "accountType")
).flatten
}
case class BankInfoData(accountNumber: Int,
bankAddress: String,
bankNumber: Int,
contactPerson: String,
phoneNumber: Int,
accountType: String) extends Fields
case class UserUpdatedFields(contactPerson: String,
phoneNumber: Int,
accountType: AccountType) extends Fields
so it was possible to call
BankInfoData(...). findDiff(UserUpdatedFields(...))
If you want to further-improve and avoid naming all the fields multiple times, for example shapeless could be used to do it compile time. Not exactly the same but something like this to get started. Or use reflection to do it runtime like this answer.
That would be a very easy task to achieve if it would be an easy way to convert case class to map. Unfortunately, case classes don't offer that functionality out-of-box yet in Scala 2.12 (as Mario have mentioned it will be easy to achieve in Scala 2.13).
There's a library called shapeless, that offers some generic programming utilities. For example, we could write an extension function toMap using Record and ToMap from shapeless:
object Mappable {
implicit class RichCaseClass[X](val x: X) extends AnyVal {
import shapeless._
import ops.record._
def toMap[L <: HList](
implicit gen: LabelledGeneric.Aux[X, L],
toMap: ToMap[L]
): Map[String, Any] =
toMap(gen.to(x)).map{
case (k: Symbol, v) => k.name -> v
}
}
}
Then we could use it for findDiff:
def findDiff(profile: Profile): Seq[String] = {
import Mappable._
profile match {
case Profile(_, _, bankInfo, Some(userUpdatedFields)) =>
val bankInfoMap = bankInfo.toMap
userUpdatedFields.toMap.toList.flatMap{
case (k, v) if bankInfoMap.get(k).exists(_ != v) => Some(k)
case _ => None
}
case _ => Seq()
}
}

List of strings to case class with inner case classes

Let's say i have 2 cases classes:
case class Money(amount: Int, currency: String)
case class Human(name: String, money: Money)
is there a nice way to "translate" a list of strings to class Human? smth like:
def superMethod[A](params: List[String]): A = ???
val params: List[Any] = List("john", 100, "dollar")
superMethod(params) // => Human("john", Money(100, "dollar"))
so essentially i know type A only in runtime
UPDATE: i found ~ what i was looking for. it seems i can do it via shapeless. example i found in github
Here is an implementation that works for generic classes A.
It relies on runtime reflection (that is, a different TypeTag can be passed to the method at runtime). The following obvious conditions must be fulfilled in order to use this method:
A must be on the class path, or otherwise be loadable by the used class loader
TypeTag must be available for A at the call site.
The actual implementation is in the Deserializer object. Then comes a little demo.
The deserializer:
import scala.reflect.runtime.universe.{TypeTag, Type}
object Deserializer {
/** Extracts an instance of type `A` from the
* flattened `Any` constructor arguments, and returns
* the constructed instance together with the remaining
* unused arguments.
*/
private def deserializeRecHelper(
flattened: List[Any],
tpe: Type
): (Any, List[Any]) = {
import scala.reflect.runtime.{universe => ru}
// println("Trying to deserialize " + tpe + " from " + flattened)
// println("Constructor alternatives: ")
// val constructorAlternatives = tpe.
// member(ru.termNames.CONSTRUCTOR).
// asTerm.
// alternatives.foreach(println)
val consSymb = tpe.
member(ru.termNames.CONSTRUCTOR).
asTerm.
alternatives(0).
asMethod
val argsTypes: List[Type] = consSymb.paramLists(0).map(_.typeSignature)
if (tpe =:= ru.typeOf[String] || argsTypes.isEmpty) {
val h :: t = flattened
(h, t)
} else {
val args_rems: List[(Any, List[Any])] = argsTypes.scanLeft(
(("throwaway-sentinel-in-deserializeRecHelper": Any), flattened)
) {
case ((_, remFs), t) =>
deserializeRecHelper(remFs, t)
}.tail
val remaining: List[Any] = args_rems.last._2
val args: List[Any] = args_rems.unzip._1
val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
val classMirror = runtimeMirror.reflectClass(tpe.typeSymbol.asClass)
val cons = classMirror.reflectConstructor(consSymb)
// println("Build constructor arguments array for " + tpe + " : " + args)
val obj = cons.apply(args:_*)
(obj, remaining)
}
}
def deserialize[A: TypeTag](flattened: List[Any]): A = {
val (a, rem) = deserializeRecHelper(
flattened,
(implicitly: TypeTag[A]).tpe
)
require(
rem.isEmpty,
"Superfluous arguments remained after deserialization: " + rem
)
a.asInstanceOf[A]
}
}
Demo:
case class Person(id: String, money: Money, pet: Pet, lifeMotto: String)
case class Money(num: Int, currency: String)
case class Pet(color: String, species: Species)
case class Species(description: String, name: String)
object Example {
def main(args: Array[String]): Unit = {
val data = List("Bob", 42, "USD", "pink", "invisible", "unicorn", "what's going on ey?")
val p = Deserializer.deserialize[Person](data)
println(p)
}
}
Output:
Person(Bob,Money(42,USD),Pet(pink,Species(invisible,unicorn)),what's going on ey?)
Discussion
This implementation is not restricted to case classes, but it requires each "Tree-node-like" class to have exactly one constructor that accepts either
primitive types (Int, Float), or
strings, or
other "Tree-node-like" classes.
Note that the task is somewhat ill-posed: what does it mean to say that all constructor arguments are flattened in a single list? Given the class Person(name: String, age: Int), will the List[Any] contain every single byte of the name as a separate entry? Probably not. Therefore, strings are handled by the deserializer in a special way, and all other collection-like entities are not supported for the same reasons (unclear where to stop parsing, because size of the collection is not known).
In case A is not a generic type, but effectively Human, you can use a companion object to the case class Human:
object Human {
def fromList(list: List[String]): Human = list match {
case List(name, amount, currency) => Human(name, Money(amount.toInt, currency))
case _ => handle corner case
}
}
Which you can call:
Human.fromList(List("john", "100", "dollar"))
To make it safe, don't forget to handle the case of lists whose size wouldn't be 3; and of lists whose 2nd element can't be cast to an Int:
import scala.util.Try
object Human {
def fromList(list: List[String]): Option[Human] = list match {
case List(name, amount, currency) =>
Try(Human(name, Money(amount.toInt, currency))).toOption
case _ => None
}
}
Edit: Based on your last comment, you might find this usefull:
case class Money(amount: Int, currency: String)
case class Human(name: String, money: Money)
case class SomethingElse(whatever: Double)
object Mapper {
def superMethod(list: List[String]): Option[Any] =
list match {
case List(name, amount, currency) =>
Try(Human(name, Money(amount.toInt, currency))).toOption
case List(whatever) => Try(SomethingElse(whatever.toDouble)).toOption
case _ => None
}
}
println(Mapper.superMethod(List("john", 100, "dollar")))
> Some(Human(john,Money(100,dollar)))
println(Mapper.superMethod(List(17d)))
> Some(SomethingElse(17.0))
or alternatively:
object Mapper {
def superMethod[A](list: List[String]): Option[A] =
(list match {
case List(name, amount, currency) =>
Try(Human(name, Money(amount, currency))).toOption
case List(whatever) =>
Try(SomethingElse(whatever.toDouble)).toOption
case _ => None
}).map(_.asInstanceOf[A])
}
println(Mapper.superMethod[Human](List("john", "100", "dollar")))
> Some(Human(john,Money(100,dollar)))
println(Mapper.superMethod[SomethingElse](List("17.2")))
> Some(SomethingElse(17.0))

getting serealizing case class with play json library

My case classes looks like this:
case class Person(personalInfo: PersonalInfo, bankInfo: BankInfo)
case class PersonalInfo(fname: String, lname: String)
case class BankInfo(atmCode: Int, creditCard: CreditCard)
case class CreditCard(number: Int, experationDate: String)
so to be able to get a person in my controller I added serealizer for person:
object PersonSerealizer {
implicit val PersonalInfoFormat: OFormat[PersonalInfo] = Json.format[PersonalInfo]
implicit val CreditCardFormat: OFormat[CreditCard] = Json.format[CreditCard]
implicit val BankInfoFormat: OFormat[BankInfo] = Json.format[BankInfo]
implicit val PersonFormat: OFormat[Person] = Json.format[Person]
}
in my controller I have a super simple action that looks like this:
i
mport serializers.PersonSerealizer._
def getBrothers(): Action[JsValue] = Action.async(parse.json) { request =>
request.body.validate[Person] match {
case JsSuccess(person, _) =>
brothersService.getBrothers(person) // this returns a List[Person]
.map(res => Future{Ok(res)})
case JsError(errors) => Future(BadRequest("Errors! " + errors.mkString))
}
}
but I get this error:
Error:(23, 81) No unapply or unapplySeq function found for class
BankInfo: / implicit val BankInfoFormat:
OFormat[BankInfo] = Json.format[BankInfo]
something is weird...from what I know it supposed to work
The order of defining your implicits seems to matter. Also, I think it is safer to use companion objects instead of defining them inside an arbitrary object.
case class PersonalInfo(fname: String, lname: String)
object PersonalInfo {
implicit val personalInfoJsonFormat = Json.format[PersonalInfo]
}
case class CreditCard(number: Int, experationDate: String)
object CreditCard {
implicit val creditCardJsonFormat = Json.format[CreditCard]
}
case class BankInfo(atmCode: Int, creditCard: CreditCard)
object BankInfo {
implicit val bankInfoJsonFormat = Json.format[BankInfo]
}
case class Person(personalInfo: PersonalInfo, bankInfo: BankInfo)
object Person {
implicit val personJsonFmt = Json.format[Person]
}
// Your action will be like this
def getBrothers() = Action.async(parse.json) { req =>
req.body.validate[Person] match {
case JsSuccess(p, _) =>
Future.successful(Ok(Json.toJson(service.getBrothers(p))))
case JsError(errors) =>
Future.successful(BadRequest(JsError.toJson(errors)))
}
}

Scalameta: Identify particular annotations

I want to auto-generate REST API models in Scala using scalameta annotation macros. Specifically, given:
#Resource case class User(
#get id : Int,
#get #post #patch name : String,
#get #post email : String,
registeredOn : Long
)
I want to generate:
object User {
case class Get(id: Int, name: String, email: String)
case class Post(name: String, email: String)
case class Patch(name: Option[String])
}
trait UserRepo {
def getAll: Seq[User.Get]
def get(id: Int): User.Get
def create(request: User.Post): User.Get
def replace(id: Int, request: User.Put): User.Get
def update(id: Int, request: User.Patch): User.Get
def delete(id: Int): User.Get
}
I have something working here: https://github.com/pathikrit/metarest
Specifically I am doing this:
import scala.collection.immutable.Seq
import scala.collection.mutable
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.meta._
class get extends StaticAnnotation
class put extends StaticAnnotation
class post extends StaticAnnotation
class patch extends StaticAnnotation
#compileTimeOnly("#metarest.Resource not expanded")
class Resource extends StaticAnnotation {
inline def apply(defn: Any): Any = meta {
val (cls: Defn.Class, companion: Defn.Object) = defn match {
case Term.Block(Seq(cls: Defn.Class, companion: Defn.Object)) => (cls, companion)
case cls: Defn.Class => (cls, q"object ${Term.Name(cls.name.value)} {}")
case _ => abort("#metarest.Resource must annotate a class")
}
val paramsWithAnnotation = for {
Term.Param(mods, name, decltype, default) <- cls.ctor.paramss.flatten
seenMods = mutable.Set.empty[String]
modifier <- mods if seenMods.add(modifier.toString)
(tpe, defArg) <- modifier match {
case mod"#get" | mod"#put" | mod"#post" => Some(decltype -> default)
case mod"#patch" =>
val optDeclType = decltype.collect({case tpe: Type => targ"Option[$tpe]"})
val defaultArg = default match {
case Some(term) => q"Some($term)"
case None => q"None"
}
Some(optDeclType -> Some(defaultArg))
case _ => None
}
} yield modifier -> Term.Param(Nil, name, tpe, defArg)
val models = paramsWithAnnotation
.groupBy(_._1.toString)
.map({case (verb, pairs) =>
val className = Type.Name(verb.stripPrefix("#").capitalize)
val classParams = pairs.map(_._2)
q"case class $className[..${cls.tparams}] (..$classParams)"
})
val newCompanion = companion.copy(
templ = companion.templ.copy(stats = Some(
companion.templ.stats.getOrElse(Nil) ++ models
))
)
Term.Block(Seq(cls, newCompanion))
}
}
I am unhappy with the following snip of code:
modifier match {
case mod"#get" | mod"#put" | mod"#post" => ...
case mod"#patch" => ...
case _ => None
}
The above code does "stringly" pattern matching on the annotations I have. Is there anyway to re-use the exact annotations I have to pattern match for these:
class get extends StaticAnnotation
class put extends StaticAnnotation
class post extends StaticAnnotation
class patch extends StaticAnnotation
It's possible to replace the mod#get stringly typed annotation with a get() extractor using a bit of runtime reflection (at compile time).
In addition, let's say we also want to allow users to fully qualify the annotation with #metarest.get or #_root_.metarest.get
All the following code examples assume import scala.meta._. The tree structure of #get, #metarest.get and #_root_.metarest.get are
# mod"#get".structure
res4: String = """ Mod.Annot(Ctor.Ref.Name("get"))
"""
# mod"#metarest.get".structure
res5: String = """
Mod.Annot(Ctor.Ref.Select(Term.Name("metarest"), Ctor.Ref.Name("get")))
"""
# mod"#_root_.metarest.get".structure
res6: String = """
Mod.Annot(Ctor.Ref.Select(Term.Select(Term.Name("_root_"), Term.Name("metarest")), Ctor.Ref.Name("get")))
"""
The selectors are either Ctor.Ref.Select or Term.Select and the names are either Term.Name or Ctor.Ref.Name.
Let's first create a custom selector extractor
object Select {
def unapply(tree: Tree): Option[(Term, Name)] = tree match {
case Term.Select(a, b) => Some(a -> b)
case Ctor.Ref.Select(a, b) => Some(a -> b)
case _ => None
}
}
Then create a few helper utilities
object ParamAnnotation {
/* isSuffix(c, a.b.c) // true
* isSuffix(b.c, a.b.c) // true
* isSuffix(a.b.c, a.b.c) // true
* isSuffix(_root_.a.b.c, a.b.c) // true
* isSuffix(d.c, a.b.c) // false
*/
def isSuffix(maybeSuffix: Term, fullName: Term): Boolean =
(maybeSuffix, fullName) match {
case (a: Name, b: Name) => a.value == b.value
case (Select(q"_root_", a), b: Name) => a.value == b.value
case (a: Name, Select(_, b)) => a.value == b.value
case (Select(aRest, a), Select(bRest, b)) =>
a.value == b.value && isSuffix(aRest, bRest)
case _ => false
}
// Returns true if `mod` matches the tree structure of `#T`
def modMatchesType[T: ClassTag](mod: Mod): Boolean = mod match {
case Mod.Annot(term: Term.Ref) =>
isSuffix(term, termRefForType[T])
case _ => false
}
// Parses `T.getClass.getName` into a Term.Ref
// Uses runtime reflection, but this happens only at compile time.
def termRefForType[T](implicit ev: ClassTag[T]): Term.Ref =
ev.runtimeClass.getName.parse[Term].get.asInstanceOf[Term.Ref]
}
With this setup, we can add a companion object to the get definition with an
unapply boolean extractor
class get extends StaticAnnotation
object get {
def unapply(mod: Mod): Boolean = ParamAnnotation.modMatchesType[get](mod)
}
Doing the same for post and put, we can now write
// before
case mod"#get" | mod"#put" | mod"#post" => Some(decltype -> default)
// after
case get() | put() | post() => Some(decltype -> default)
Note that this approach will still not work if the user renames for example get on import
import metarest.{get => GET}
I would recommend aborting if an annotation does not match what you expected
// before
case _ => None
// after
case unexpected => abort("Unexpected modifier $unexpected. Expected one of: put, get post")
PS. The object get { def unapply(mod: Mod): Boolean = ... } part is boilerplate that could be generated by some #ParamAnnotation macro annotation, for example #ParamAnnotion class get extends StaticAnnotation