I am performing a repetitive task of creating a object that has same internal contents and hence thought of creating a generic method that would help me achieve this.
The internal object is as follows
case class Data(value: Int)
I have a base trait as follows
trait Base
There are a couple of classes that extend this trait
case class A(data: Data) extends Base
case class B(data: Data) extends Base
case class C(data: Data) extends Base
The generic method that I am trying to write to create the object
def getObject[T <: Base](data: Data, t: T) = {
T(data)
}
However, while trying to do so, I get a compile-time error saying that
Cannot resolve symbol T
Can you please let me know what I am missing in this method implementation.
Note:- This is a very simplistic implementation of what I am trying to do in my code.
Due to type-erasure, you can't use generic type to create a new instance of an object.
You could use ClassTag to capture a class of T at runtime:
case class Data(value: Int)
trait Base
case class A(data: Data) extends Base
case class B(data: Data) extends Base
case class C(data: Data) extends Base
def getObject[T <: Base](data: Data)(implicit ct: ClassTag[T]): T =
ct.runtimeClass.getDeclaredConstructors.head.newInstance(data).asInstanceOf[T]
val a: A = getObject[A](Data(1))
val b: B = getObject[B](Data(2))
val c: C = getObject[C](Data(3))
As cchantep noticed, it has a drawback, that if your case class doesn't have a constructor with single argument Data, this function will fail at runtime.
Consider typeclass solution for compile-time safety
final case class Data(value: Int)
final case class A(data: Data)
final case class B(data: Data)
final case class C(data: Data)
trait BaseFactory[T] {
def apply(data: Data): T
}
object BaseFactory {
def apply[T](data: Data)(implicit ev: BaseFactory[T]): T = ev.apply(data)
implicit val aBaseFactory: BaseFactory[A] = (data: Data) => A(data)
implicit val bBaseFactory: BaseFactory[B] = (data: Data) => B(data)
implicit val cBaseFactory: BaseFactory[C] = (data: Data) => C(data)
}
val data = Data(42)
BaseFactory[A](data) // res0: A = A(Data(42))
BaseFactory[B](data) // res1: B = B(Data(42))
BaseFactory[C](data) // res2: C = C(Data(42))
Related
I'm running into an issue where I am working with several Traits that use dependent typing, but when I try to combine the Traits in my business logic, I get a compilation error.
import java.util.UUID
object TestDependentTypes extends App{
val myConf = RealConf(UUID.randomUUID(), RealSettings(RealData(5, 25.0)))
RealConfLoader(7).extractData(myConf.settings)
}
trait Data
case class RealData(anInt: Int, aDouble: Double) extends Data
trait MySettings
case class RealSettings(data: RealData) extends MySettings
trait Conf {
type T <: MySettings
def id: UUID
def settings: T
}
case class RealConf(id: UUID, settings: RealSettings) extends Conf {
type T = RealSettings
}
trait ConfLoader{
type T <: MySettings
type U <: Data
def extractData(settings: T): U
}
case class RealConfLoader(someInfo: Int) extends ConfLoader {
type T = RealSettings
type U = RealData
override def extractData(settings: RealSettings): RealData = settings.data
}
The code in processor will not compile because extractData expects input of type ConfLoader.T, but conf.settings is of type Conf.T. Those are different types.
However, I have specified that both must be subclasses of MySettings, so it should be the case I can use one where the other is desired. I understand Scala does not compile the code, but is there some workaround so that I can pass conf.settings to confLoader.extractData?
===
I want to report that for the code I wrote above, there is a way to write it that would decrease my usage of dependent types. I noticed today while experimenting with Traits that Scala supports subclassing on defs and vals on classes that implement the Trait. So I only need to create a dependent type for the argument for extractData, and not the output.
import java.util.UUID
object TestDependentTypes extends App{
val myConf = RealConf(UUID.randomUUID(), RealSettings(RealData(5, 25.0)))
RealConfLoader(7).extractData(myConf.settings)
def processor(confLoader: ConfLoader, conf: Conf) = confLoader.extractData(conf.settings.asInstanceOf[confLoader.T])
}
trait Data
case class RealData(anInt: Int, aDouble: Double) extends Data
trait MySettings
case class RealSettings(data: RealData) extends MySettings
trait Conf {
def id: UUID
def settings: MySettings
}
case class RealConf(id: UUID, settings: RealSettings) extends Conf
trait ConfLoader{
type T <: MySettings
def extractData(settings: T): Data
}
case class RealConfLoader(someInfo: Int) extends ConfLoader {
type T = RealSettings
override def extractData(settings: RealSettings): RealData = settings.data
}
The above code does the same thing and reduces dependence on dependent types. I have only removed processor from the code. For the implementation of processor, refer to any of the solutions below.
The code in processor will not compile because extractData expects input of type ConfLoader.T, but conf.settings is of type Conf.T. Those are different types.
In the method processor you should specify that these types are the same.
Use type refinements (1, 2) for that: either
def processor[_T](confLoader: ConfLoader { type T = _T }, conf: Conf { type T = _T }) =
confLoader.extractData(conf.settings)
or
def processor(confLoader: ConfLoader)(conf: Conf { type T = confLoader.T }) =
confLoader.extractData(conf.settings)
or
def processor(conf: Conf)(confLoader: ConfLoader { type T = conf.T }) =
confLoader.extractData(conf.settings)
IMHO if you don't need any of the capabilities provided by dependent types, you should just use plain type parameters.
Thus:
trait Conf[S <: MySettings] {
def id: UUID
def settings: S
}
final case class RealConf(id: UUID, settings: RealSettings) extends Conf[RealSettings]
trait ConfLoader[S <: MySettings, D <: Data] {
def extractData(settings: S): D
}
final case class RealConfLoader(someInfo: Int) extends ConfLoader[RealSettings, RealData] {
override def extractData(settings: RealSettings): RealData =
settings.data
}
def processor[S <: MySettings, D <: Data](loader: ConfLoader[S, D])(conf: Conf[S]): D =
loader.extractData(conf.settings)
But, if you really require them to be type members, you may ensure both are the same.
def processor(loader: ConfLoader)(conf: Conf)
(implicit ev: conf.S <:< loader.S): loader.D =
loader.extractData(conf.settings)
Consider this code:
sealed trait Data
case class StringData(string: String) extends Data
case class IntData(int: Int) extends Data
trait Reader[A] {
def read(data: Data): A
}
implicit val stringReader: Reader[String] = {
case StringData(string) => string
case _ => sys.error("not a string")
}
implicit val intReader: Reader[Int] = {
case IntData(int) => int
case _ => sys.error("not an int")
}
With this in scope, I want to write an implicit method that silently converts from Data values to their "real" Scala values.
implicit def fromData[A: Reader](data: Data): A =
implicitly[Reader[A]].read(data)
But then, this code does not compile:
val str: String = StringData("foo")
val int: Int = IntData(420)
The error is a type mismatch. Standard debugging methods for implicits show that the A from fromData can't be infered (all implicit Readers are shown as applicable).
For your convenience, this is a link to a scastie of the code. In this other scastie, a similiar, yet different, and working snippet is presented.
My question: What is going on here?
Making the change to your Data class as well as your reader implicit conversion as below allows the code to compile.
import scala.language.implicitConversions
sealed trait Data[A]
case class StringData(string: String) extends Data[String]
case class IntData(int: Int) extends Data[Int]
trait Reader[A] {
def read(data: Data[A]): A
}
implicit val stringReader: Reader[String] = {
case StringData(string) => string
case _ => sys.error("not a string")
}
implicit val intReader: Reader[Int] = {
case IntData(int) => int
case _ => sys.error("not an int")
}
implicit def fromData[A](data: Data[A])(implicit ev: Reader[A]): A = ev.read(data)
val str: String = StringData("foo")
val int: Int = IntData(420)
You need data to be typed so that the compiler can infer which Reader should be used based on the type parameter of Data. Also notice that the following would not compile if you did not define an implicit Reader.
case class DoubleData(d: Double) extends Data[Double]
// Fails compilation because no implicit Reader exists.
val d: Double = DoubleData(5d)
I want to define circular referenced structure JSON with spray-json, so I try to define like below.
final case class A(b: B)
final case class B(a: A)
trait AProtocol extends DefaultJsonProtocol {
implicit val aProtocol: RootJsonFormat[A] = rootFormat(lazyFormat(jsonFormat1(A)))
}
But I got an error which is
<console>:18: error: could not find implicit value for evidence parameter of type MyProtocol.this.JF[B]
implicit val aProtocol: RootJsonFormat[A] = rootFormat(lazyFormat(jsonFormat1(A)))
Please give me some advices.
Well, you have jsonFormat for A but what with B. You are using lazyFormat well but completely forgot about other dependency. Try this:
final case class A(b: B)
final case class B(a: A)
trait AProtocol extends DefaultJsonProtocol {
implicit val aProtocol: RootJsonFormat[A] = rootFormat(lazyFormat(jsonFormat1(A)))
implicit val bProtocol: RootJsonFormat[B] = rootFormat(lazyFormat(jsonFormat1(B)))
}
I have the following typeclass:
trait Loader[A, B, C] {
//Any Spark loader requires
// A -> Input Type
// B -> Output Type
// C -> some type of implicit context provided by the compiler from
// the sourounding environment.
def load(input: A)(implicit context: C): B
}
object Loader {
implicit object HiveLoader extends Loader[HiveTableSource, DataFrame, HiveContext] {
def load(source: HiveTableSource)(implicit hc: HiveContext): DataFrame = {
val db = source.db
val tbl = source.tbl
val df = hc.sql(s"select * from $db.$tbl")
df
}
}
def loadDataSource[A,B,C](d: A)(implicit ldr: Loader[A,B,C], context: C):B =
ldr.load(d)
}
sealed trait DataSource
case class HiveTableSource(db: String, tbl: String) extends DataSource
When I try the following, the code fails to compile with "could not find implicit parameter ldr"
c // this is of type DataSource
import Loader._
loadDataSource(c) //This Fails
However if I explicitly force the type
LoadDataSource(c.asInstanceof[HiveTableSource]) The code compiles.
Using asInstanceOf is recipe for disaster. See the "The Scalazzi Safe Scala Subset".
However, if you treat DataSource as an ADT (algebraic data type), then you could get around this using pattern matching, but you'd have to select the instance of the typeclass yourself.
For that to be possible, there has to be a restrict set of possible data sources (much like Option[A] is restricted to Some[A] and None). I see you've sealed your DataSource trait so you should be ok.
sealed trait DataSource
final case class HiveTableSource(db: String, tbl: String) extends DataSource
final case class SomeOtherSource() extends DataSource
val c: DataSource = ???
c match {
case s: HiveTableSource => loadDataSource(s)(HiveLoader)
case s: SomeOtherSource => loadDataSource(s)(SomeOtherLoader)
}
I want to write fail-safe wrapper for spary-json str.parseJson.convertTo[A].
It must have logic - "when I can't parse json as case class A, I try parse it as case class Error"
def parse(str:String) =
try {
str.parseJson.convertTo[A]
} catch {
case e:Exception => str.parseJson.convertTo[Error]
}
but also I want to make class A a parameter.
def parse[A<:Obj](str:String):Obj = {
import JsonProtocols._
try {
str.parseJson.convertTo[A]
} catch {
case e:Exception => str.parseJson.convertTo[Error]
}
}
using:
...
trait Obj
case class Error(error:String) extends Obj
case class DataA(a1:String, a2: Int) extends Obj
case class DataB(b1:String, b2: Boolean) extends Obj
object JsonProtocols extends DefaultJsonProtocol {
implicit val errorFormat = jsonFormat1(Error)
implicit val dataAFormat = jsonFormat2(DataA)
implicit val dataBFormat = jsonFormat2(DataB)
...
}
...
parse[DataA]("...json...") match {
case obj: DataA => "..."
case obj: Error => "..."
}
...
I get compiling error:
Error:(25, 30) Cannot find JsonReader or JsonFormat type class for A
str.parseJson.convertTo[A]
^
How can I fix this error?
Can I do this by another way?
Simplifying things, looks like that you've:
defined 3 case classes with appropriate JsonReaders
defined a generic function, which type is lower bound to Obj.
The compiler tells you that it cannot find a JsonReader for all possible classes implementing trait Obj, because you have defined only specific JsonReaders for Error, DataA and DataB.
To solve the problem you can use Either[T,Error] type for deserialization like:
sealed trait Obj
case class Error(error:String) extends Obj
case class DataA(a1:String, a2: Int) extends Obj
case class DataB(b1:String, b2: Boolean) extends Obj
val strA = """{"a1":"foo", "a2": 1}"""
val strB = """{"b1":"bar", "b2": false}"""
val srtE = """{"error": "oops"}"""
object JsonProtocols extends DefaultJsonProtocol {
implicit val errorFormat = jsonFormat1(Error)
implicit val dataAFormat = jsonFormat2(DataA)
implicit val dataBFormat = jsonFormat2(DataB)
}
import JsonProtocols._
val result:Obj = strA.parseJson.convertTo[Either[DataA,Error]] match {
case Left(dataA) => dataA
case Right(error) => error
}