Say I have the following trait:
trait T {
val x: Int
}
def foo(i: Int): T
I would like to bet able to write and read this trait using upickle without knowing what the specific class is. e.g.:
upickle.default.write(foo(3))
Such that I could elsewhere define foo to be something like:
case class A(x: Int)
def foo(i: Int): T = A(i)
I am thinking I need to define an implicit Writer as a member of T but I don't know what the appropriate syntax would be?
trait T {
val x: Int
}
object T {
implicit val rw: ReaderWriter[T] = ...
}
The problem is what to put into the ... part: if you have a T value, you can just store its x:
... = readwriter[Int].bimap[T](
t => t.x,
i => new T { val x = i }
)
The problem with this solution is that reading a written A(3) won't return an A. And this isn't really solvable without making T sealed or otherwise handling a specific set of subclasses only.
You could include a class name as well when writing, but that won't help if the class has any fields other than x to store:
class B(override val x: Int, y: String) extends T
If T is sealed, you just write
... = macroRW
Related
Let's say I have the following hierarchy:
abstract class A(val x: Int, val y: String)
class B(override val x: Int, override val y: String, val z: Int) extends A(x,y)
Now I want to initialize the values from a configuration object but I want the actual values to be the original ones.
If I would do the configuration in B only then I would do something like:
class B(override val x: Int, override val y: String, val z: Int) extends A(x,y)
def this(conf: Conf) {
this(conf.get("x"), conf.get("y"), conf.get("z"))
}
but I want to be able to do the same in A.
If I add:
abstract class A(val x: Int, val y: String)
this(conf: Conf) {
this(conf.get("x"), conf.get("y))
}
I wouldn't be able to define B (I don't have the conf in the B default constructor).
EDIT:
To make this clearer:
The use case I have is a factory which generates the proper B (there are a large number of child classes). It does so by doing something like:
def getElement(elemType: String, conf: Conf): A = {
elemType match {
case "B" => new B(conf)
}
}
Currently, I have a companion object:
object B {
def apply(conf: conf) = B(conf.getx(), conf.gety(), ...)
}
The problem is that when I need to add a new element to the parent A, I need to go and change every one of the children and I have the same code conf.getx(), conf.gety() etc.
Ideally I would like B constructor to be able to do something like:
class B(conf: Conf) extends A(conf)
but I can't do this as this would make conf into a member of B.
You can also use companion objects to define alternative constructors:
case object A {
def apply(conf: Conf): A = new A(conf.get("x"), conf.get("y"))
}
case object B {
def apply(conf: Conf): B = new B(conf.get("x"), conf.get("y"), conf.get("z"))
}
After looking around some more I found this (also points to this) and this. All three basically say the following:
If we use an argument without val or var and it is only referenced in the constructor then it does NOT become a member.
This means the following solution would work:
abstract class A(conf: Conf) {
val x = conf.getX()
val y = conf.getY()
}
class B(conf: Conf) extends A(conf) {
val z = conf.getZ()
}
would provide the required behavior cleanly and simply.
Given the following classes:
case class AddRequest(x: Int, y: Int)
case class AddResponse(sum: Int)
case class ToUppercaseRequest(str: String)
case class ToUppercaseResponse(upper: String)
How do I define in a typesafe manner some function:
def process(req: ???): ???
Such that the following should hold true:
val r1: AddResponse = process(AddRequest(2, 3))
val r2: ToUppercaseResponse = process(ToUppercaseRequest("aaa"))
Also, the following should not compile:
val r3 = process("somestring")
This is both entirely possible and a totally reasonable thing to do in Scala. This kind of thing is all over Shapeless, for example, and something similar (but less principled) is the basis of the magnet pattern that shows up in Spray, etc.
Update: note that the following solution assumes that "given the following classes" means you don't want to touch the case classes themselves. If you don't care, see the second part of the answer below.
You'd want a type class that maps input types to output types:
case class AddRequest(x: Int, y: Int)
case class AddResponse(sum: Int)
case class ToUppercaseRequest(str: String)
case class ToUppercaseResponse(upper: String)
trait Processable[In] {
type Out
def apply(in: In): Out
}
And then some type class instances:
object Processable {
type Aux[I, O] = Processable[I] { type Out = O }
implicit val toUppercase: Aux[ToUppercaseRequest, ToUppercaseResponse] =
new Processable[ToUppercaseRequest] {
type Out = ToUppercaseResponse
def apply(in: ToUppercaseRequest): ToUppercaseResponse =
ToUppercaseResponse(in.str.toUpperCase)
}
implicit val add: Aux[AddRequest, AddResponse] =
new Processable[AddRequest] {
type Out = AddResponse
def apply(in: AddRequest): AddResponse = AddResponse(in.x + in.y)
}
}
And now you can define process using this type class:
def process[I](in: I)(implicit p: Processable[I]): p.Out = p(in)
Which works as desired (note the appropriate static types):
scala> val res: ToUppercaseResponse = process(ToUppercaseRequest("foo"))
res: ToUppercaseResponse = ToUppercaseResponse(FOO)
scala> val res: AddResponse = process(AddRequest(0, 1))
res: AddResponse = AddResponse(1)
But it doesn't work on arbitrary types:
scala> process("whatever")
<console>:14: error: could not find implicit value for parameter p: Processable[String]
process("whatever")
^
You don't even have to use a path dependent type (you should be able just to have two type parameters on the type class), but it makes using process a little nicer if e.g. you have to provide the type parameter explicitly.
Update: everything above assumes that you don't want to change your case class signatures (which definitely isn't necessary). If you are willing to change them, though, you can do this a little more concisely:
trait Input[Out] {
def computed: Out
}
case class AddRequest(x: Int, y: Int) extends Input[AddResponse] {
def computed: AddResponse = AddResponse(x + y)
}
case class AddResponse(sum: Int)
case class ToUppercaseRequest(str: String) extends Input[ToUppercaseResponse] {
def computed: ToUppercaseResponse = ToUppercaseResponse(str.toUpperCase)
}
case class ToUppercaseResponse(upper: String)
def process[O](in: Input[O]): O = in.computed
And then:
scala> process(AddRequest(0, 1))
res9: AddResponse = AddResponse(1)
scala> process(ToUppercaseRequest("foo"))
res10: ToUppercaseResponse = ToUppercaseResponse(FOO)
Which kind of polymorphism (parametric or ad-hoc) you should prefer is entirely up to you. If you want to be able to describe a mapping between arbitrary types, use a type class. If you don't care, or actively don't want this operation to be available for arbitrary types, using subtyping.
You can define a common trait for Requests, and a common trait for Responses where the request type is defined for specific response type:
trait Request[R <: Response]
trait Response
case class AddRequest(x: Int, y: Int) extends Request[AddResponse]
case class AddResponse(sum: Int) extends Response
case class ToUppercaseRequest(str: String) extends Request[ToUppercaseResponse]
case class ToUppercaseResponse(upper: String) extends Response Response[ToUppercaseRequest]
Then, process signature would be:
def process[A <: Request[B], B <: Response](req: A): B
When you call process, you'll have to explicitly define the types so that the returned type is what you expect it to be - it can't be inferred specifically enough:
val r1: AddResponse = process[AddRequest, AddResponse](AddRequest(2, 3))
val r2: ToUppercaseResponse = process[ToUppercaseRequest, ToUppercaseResponse](ToUppercaseRequest("aaa"))
Let us assume we have a trait T. What is the best way to achieve the following:
Everybody who writes an implementation of T should be forced to provide a possibility that allows a parameter-free initialization of T, i.e., we probably have to enforce the implementation of a configurable factory.
All logic/data that only depends on the actual initialization parameters (of a certain implementation A of T) should be handled/stored centrally, but should be available in both the factory and A.
The most simple/concise way I see to achieve this (approximately) would be to add a trait for a factory and link T to this factory:
trait T {
val factory: TFactory
}
trait TFactory {
def build(): T
val description: String // example for logic/data that only depend on the parameters
}
// example implementation:
class A(val factory: AFactory, paramA: Int, paramB: Int, paramC: Int) extends T
class AFactory(paramA: Int, paramB: Int, paramC: Int) extends TFactory {
def build = new A(this, paramA, paramB, paramC)
val description = f"$paramA $paramB $paramC"
}
Obviously this does not really "enforce" the implementation of a factory (as long as there is an alternative implementation available) and obviously it is possible to generate instantiations of A which link to a "wrong" TFactory. What I also don't like about this approach is the repetition of the initialization parameters. I often create yet another class AParams which again wraps all parameters (for instance to facilitate adding new parameters). Thus, I end up with three classes, which imho is a lot of boilerplate for this simple problem.
My question is whether there is a (maybe completely) different approach, which achieves the same primary goals but is more concise?
I'm not quite sure I get the full intent of your requirements but what do you think of this behavior?
trait TFactory{
def build():T
val description:String
}
trait T extends TFactory
//can't declare A without build and not make it abstract
class A(paramA: Int, paramB: Int, paramC: Int) extends T {
def build = new A(paramA, paramB, paramC)
val description = f"$paramA $paramB $paramC"
}
val a1 = new A(1, 4, 5)
val a2 = a1.build()
//We can give ourselves as a factory to something that expects TFactory
val factory:TFactory = a1
val a_new = factory.build()
//More likely we can just give our build method
def func(f: ()=>T) = {
val new_t = f()
new_t
}
val a_newer = func(a1.build)
println(a1 +": " + a1.description)
println(a2 +": " + a2.description)
println(a_new +": " + a_new.description)
println(a_newer +": " + a_newer.description)
Output:
Main$$anon$1$A#69267649: 1 4 5
Main$$anon$1$A#69b1fbf4: 1 4 5
Main$$anon$1$A#24148662: 1 4 5
Main$$anon$1$A#3f829e6f: 1 4 5
Add a representation type parameter:
trait Factory[Prod] {
def build(): Prod
}
trait Prod[Repr] {
def factory: Factory[Repr]
}
Or, if you want to "enforce" that the type remains the same (I wouldn't do that unless you gain something from it):
trait Prod[Repr <: Prod[Repr]] {
def factory: Factory[Repr]
}
Then:
case class AConfig(a: Int, b: Int)
case class A(config: AConfig) extends Prod[A] {
def factory = AFactory(config)
}
case class AFactory(config: AConfig) extends Factory[A] {
def build() = A(config)
}
val f0 = AFactory(AConfig(1, 2))
val p0 = f0.build()
val f1 = p0.factory
val p1 = f1.build()
assert(p0 == p1)
situation:
trait Operation {
def something: Double
}
trait OperationPlus { this: A =>
override def something: Double = x + y
}
trait OperationMinus { this: A =>
override def something: Double = x - y
}
case class A(x: Double, y: Double) { this: Operation =>
}
val a = new A(1.0, 2.0) with OperationPlus
println(a.something)
error:
class A cannot be instantiated because it does not conform to its self-type A with Operation
Also, i can't instantiate A.
I tried multiple different approaches, but none provided what i'm looking for. I don't want to use case class inheritance, or give up case classes, and ideally trait / self types / something else should do the trick. Any ideas?
Update
preferred solution
trait Operation { this: A =>
def something: Double
}
trait OperationPlus extends Operation { this: A =>
override def something: Double = x + y
}
trait OperationMinus extends Operation { this: A =>
override def something: Double = x - y
}
abstract case class A(val x: Double, val y: Double) extends Operation
val a = new A(1.0, 2.0) with OperationPlus
println(a.something)
val b = new A(1.0, 2.0) with OperationMinus
println(b.something)
possible solution 1:
trait Operation {
def x:Double
def y:Double
def something: Double
}
trait OperationPlus extends Operation {
override def something: Double = x + y
}
trait OperationMinus extends Operation {
override def something: Double = x - y
}
abstract case class A(val x: Double, val y: Double) extends Operation
By using conventional classes, simple trait inheritance and a self-type in the actual value is possible to define it and supply behaviour dynamically.
Unfortunately, I have to redefine the fields in the trait. I guess is a fair compromise. Would be interested to know if somebody knows of another approach.
Thanks
Not sure about your use case, but you need to define class A like this if you want to make it work:
abstract case class A(x: Double, y: Double) extends Operation
But I don't think is very idiomatic approach to use case classes. They are mostly used as data containers and normally do not contain any behavior. (maybe you can tell some more information about things you want to achieve with this)
First, you should have:
trait OperationPlus extends Operation
trait OperationMinus extends Operation
Second, you cannot define A as case class, since that automatically defines the apply method on the companion object, where new A is called (with arguments). This call fails due to the wrong self type (the error you see).
Remove the case. If you need pattern matching, define an extractor yourself (implement unapply):
class A(val x: Double, val y: Double) { this: Operation => }
object A {
def unapply(v: A) = Some((v.x, v.y))
}
I got some difficulties designing my case classes. A simplified version looks like:
abstract class Base(s: Option[String]) {
//code
}
case class CaseClass(s: Option[String] = None) extends Base(s) {
//code
}
And I have a method where I want to do something like:
def method(base : Base) = {
//code
base copy (s = Some("string"))
}
Of course I get:
value copy is not a member of Base
So what I want to do is create a new instance based on my base class (which is not a case class). Obviously one can not do this. But how would you solve this in a elegant way?
Thanks in advance!
If you parameterize your base class and also define the abstract copy method there, you can have the subclasses return instances of their own types from the copy method. In this case, you want CaseClass to return a CaseClass, presumably.
abstract class Base[T](s: Option[String]) {
def copy(in: Option[String]) : T
}
case class CaseClass(s: Option[String]) extends Base[CaseClass](s) {
def copy(in: Option[String]) = CaseClass(in)
}
case class OtherClass(s: Option[String]) extends Base[OtherClass](s) {
def copy(in: Option[String]) = OtherClass(in)
}
def method[T <: Base[T]](base: T) : T = {
base.copy(Some("String"))
}
scala> method(CaseClass(None))
res1: CaseClass = CaseClass(Some(String))
scala> method(OtherClass(Some("hi")))
res2: OtherClass = OtherClass(Some(String))
Other subclasses of Base would return their own types. The type parameter on #method is defined with an upper bound of Base[T]. This means that T must be any sub-type of Base[T] and is what allows you to supply instances of CaseClass and OtherClass as parameters to that method.
The behavior you're trying to achieve is not implementable. copy method of a case class is autogenerated by the compiler, and once you add a method called copy to your implementation, compiler will not generate any sugar.
You can reimplement copy with traits, but it will not be as flexible as the generated one (you will have to update the base trait, copy and method implementations every time the field-set of a case class changes):
sealed trait Base[T] {
val s: Option[String]
def copy(s: Option[String]) : T
}
case class CaseClass(override val s: Option[String] = None) extends Base[CaseClass] {
override def copy(s: Option[String]) = CaseClass(s)
}
def method[T <: Base[T]](base : Base[T]) = base copy (s = Some("strng"))
Alternatively, you can implement method as follows:
case class CaseClass(s: Option[String] = None)
def method[X <: {def copy(s: Option[String]):X}](base : X) =
base copy(s = Some("string"))
scala> method(CaseClass())
res4: CaseClass = CaseClass(Some(string))
Thus you won't need Base trait, and reduce the number of alterations, if your case classes change.