During some simple scala coding exercise I ran into ideological problem of case classes without parameters and constructor parameters duplication.
It all started with the following two quite simple classes:
trait Namespace
case class Reply[T](namespace: Namespace, correlation: String, data: Try[T])
abstract class Request(val namespace: Namespace, val id: String = UUID.randomUUID().toString) {
def success[T](data: T) = Reply(namespace, id, Try(data))
def failure(msg: String) = Reply(namespace, id, Failure(new RuntimeException(msg)))
}
Now let's assume i have an entity Post and I want to add All class as a command to query all records of type Post. In my current set up it would be easier to actually write the following:
case class All extends Request(Posts)
However in this case I get compiler warning that case classes without parameters are deprecated. So one might suggest to rewrite it into the following:
case object All extends Request(Posts)
However in this case object All will be instantiated only once along with its id field which would like to avoid having unique id for each request.
Could you please suggest a better way of representing All command so that it would not be required to duplicate constructor arguments?
Thanks in advance?
The actual warning is that
case classes without a parameter list are not allowed; use either case
objects or case classes with an explicit `()' as a parameter list.
So give this class an empty parameter list, just as suggested by the compiler:
case class All() extends Requests(Posts)
Don't use a case class or case object, just use a companion apply instead. You don't really want an object here anyway, if you need a unique ID for every request.
class All extends Requests(Posts)
object All {
def apply(): All = new All()
}
getPosts(All())
Related
I'm toying with Scala for the first time so bear with me. Also using tapir to declare an API, where I'm having issues providing a Schema for an enum.
I have a bunch of enums defined that are part of my domain model and that extend Scala's Enumeration. For instance, this is one of them:
object Status extends Enumeration with JsonEnumeration {
val Active, Completed, Archived, Deleted = Value
}
And also have many case classes that uses them. For instance, Order uses our previously defined enumeration, like:
case class Order(
id: String,
name: Option[String],
status: Status.Value,
)
I want to make this enum implement a trait that adds an implicit, but without modifying the original Status enumeration (I don't want to couple the Status enum -and all the others- to this trait).
The trait looks like:
import sttp.tapir.{Schema, Validator}
trait TapirEnumeration { e: Enumeration =>
implicit def schemaForEnum: Schema[e.Value] =
Schema.string.validate(Validator.enumeration(e.values.toList, v => Option(v)))
}
I wanted to somehow modify the Order object so the Status enum is now a TapirStatus enum (or something like that) which extends both the original Status and TapirEnumeration, but I don't think that can be doable, given that Status is originally defined as a companion object.
Ideally, all the enums I want to expose as responses from my API will implement that TapirEnumeration trait while still extending what they already extend.
What can I do to achieve this? Of course, creating a new enum that implements the trait isn't DRY so it's not an option.
Why does implicit need to be defined in the enum itself in the first place? Just make it its own definition.
import scala.language.implicitConversions
object EnumImplicits {
implicit def schema[E <: Enumeration](e: E): Schema[e.Value] = ???
}
Then, wherever you need access to that implicit you just make it available with import EnumImplicits._
Here is an example
I want to limit the construction of a case class to certain types and then be able to marshall that data back and forth.
For example, let's say I have a "home" case class that takes in a "kind" argument. I want to restrict the "kind" argument to a list of approved housing types, e.g., Apartment, Condo, etc.
object Home {
// this will create an implicit conversion to json object
implicit lazy val jsFormat = Jsonx.formatCaseClass[Home]
}
case class Home(owner: String, kind: HousingType)
What I need now is a way to marshall the various child types of HousingType. For example, here are some relationships:
trait HousingType
case object Apartment extends HousingType
case object Condo extends HousingType
Predictably, attempting to use this without specifying an implicit conversion yields the following error:
"could not find implicit value for parameter helper: ... HousingType"
Is there a way to create a generic implicit conversion for this?
You have to specify how your JSON marshaller has to transform your case object, as you have case class, it's quite simple for JSON marshaller to follow default behavior - take JSON field names from a case class and their type.
You need to indicate how to marshall/unmarshall case object directly, for instance via an implicit conversion.
implicit object HousingTypeMarshaller extends Writes[HousingType] {
def writes(housingType: HousingType) = housingType match {
case Apartment => Json.toJson("Apartment")
case Condo => Json.toJson("Condo")
}
}
p.s. I use usual play.json in this example because I didn't find any reason to use Jsonx, suggest you faced a limitation on 22 fields with Play Json, usual Play Json is suitable for this situation with case object.
I have an abstract class Model from which I create case classes:
abstract class Model
case class User(.) extends Model
an abstract class Table taking such a Model as type parameter, used in one of its default concrete methods:
abstract class Table[M <: Model] {
def parser = SomeExternalBuilder[M]
}
The meaning is rather simple: "Give every instance of Table a default parser based on its own class".
The problem is that SomeExternalBuilder will only accept a case class as argument ("case class expected: M"), so it does not compile.
Can I make Table take only case classes as type parameter?
I have seen a few answers providing a missing copy method (ref1, ref2), so I tried this:
trait Model[T] {
def copy: T
}
abstract class Table[M <: Model[M]]
but now case class User extends Model[User] and must overwrite copy too, every function creating a Model takes a type parameter, and honestly the code quickly starts being atrocious, all that for that single line in Table.
Is there no better way than copying that def parser line in every child's body?
Edit: N.B. The real function is def parser: anorm.Macro.namedParser[M] from the "anorm" library for Play.
Edit: Source of the type check by this macro: https://github.com/playframework/anorm/blob/0a1b19055ba3e3749044ad8a54a6b2326235f7c8/core/src/main/scala/anorm/Macro.scala#L117
The problem is that SomeExternalBuilder will only accept a case class as argument ("case class expected: M"), so it does not compile.
I don't think you can ever get such a message from Scala compiler itself, which means that SomeExternalBuilder.apply is a macro. It requires a specific case class in order to know its fields, so that it doesn't matter if you could limit M to be a case class (which you can't): it still wouldn't accept a type parameter.
What you can do is create a macro annotation, so that when you write e.g.
#HasModel
class SomeTable extends Table[SomeModel] {
...
}
the val parser = namedParser[SomeModel] is generated automatically.
Alternately, write #HasModel[SomeModel] class SomeTable { ... } and generate extends Table[SomeModel] as well.
It wouldn't be hard (as macros go), but you still need to annotate each class extending Table.
Not fool proof solution but worth a try
case classes extend Product and Serialisable. Constraint Product with Serialisable will help you get some type safety. M can be any class which extends Product with Serialisable. But Product is extended by case class mostly
abstract class Table[M <: (Product with Serializable)] {
def parser = SomeExternalBuilder[M]
}
If I want a case class that cannot be manually constructed from outside a package, standard way would be something like this:
case class Foo private[p](a:A,b:B)
object Foo{
def apply(c:C) = {
require tit
require tat
Foo(c.a,c.b)
}
}
Any way to do that if the class looks like this:
case class Bar[T <: MySomething[T]] private[p](t:T)
or will I have to content myself with writing a def that takes care of the case class creation and must be explicitly called?
Edit
Seems I wasn't clear about what my problem was ...
How can I pass the required parameters to the object / apply function?
I would like to know if it is possible to abstract the copy method of case classes. Basically I have something like sealed trait Op and then something like case class Push(value: Int) extends Op and case class Pop() extends Op.
The first problem: A case class without arguments/members does not define a copy method. You can try this in the REPL.
scala> case class Foo()
defined class Foo
scala> Foo().copy()
<console>:8: error: value copy is not a member of Foo
Foo().copy()
^
scala> case class Foo(x: Int)
defined class Foo
scala> Foo(0).copy()
res1: Foo = Foo(0)
Is there a reason why the compiler makes this exception? I think it is rather unituitive and I would expect every case class to define a copy method.
The second problem: I have a method def ops: List[Op] and I would like to copy all ops like ops map { _.copy() }. How would I define the copy method in the Op trait? I get a "too many arguments" error if I say def copy(): Op. However, since all copy() methods have only optional arguments: why is this incorrect? And, how do I do that correct? By making another method named def clone(): Op and write everywhere def clone() = copy() for all the case classes? I hope not.
You seem to be confusing copy with clone. The goal of copy is to make an almost identical copy, but with something changed. What that something might be depends on the parameters of the case class, so it's not possible to make it a common method.
In the case of case class X(), it doesn't make much sense to have a copy method, as there's nothing there to be changed.
On the other hand, clone is a Java method whose goal is to produce perfect copies of an object, which seems to be what you want.
What would be the benefit of a compiler generated copy method for case classes without any arguments? This would just return a new Foo, and not copy anything.
To quote Lukas Rytz (I believe he implemented it):
The copy methods are only generated if there is no member named"copy" in the class, directly defined or inherited.
Upvoted Ben's answer. But what if you wanted to something like this:
sealed trait Op
case class Push(value: Int, context:String) extends Op
case class Pop(context:String) extends Op
val stackOps = List(Push(3, "foo"), Pop("foo"))
def copyToContext(newContext:String, ops:List[Op]): List[Op] = {
// ... ?
}
val changedOps = copyToContext("bar", stackOps)
// would return: List(Push(3, "bar"), Pop("bar"))
As Mirko correctly pointed out, you cannot really abstract over copy method. I support Daniel's view, that cloning may be what you want, although I would wrap it with some helper code to reduce boilerplate.
You can define a mixin trait with copy functionality and just mix it into your case classes then:
trait ClonableAs[T] extends Cloneable { this: T =>
def makeClone() = super.clone().asInstanceOf[T]
}
case class Foo(i: Int) extends ClonableAs[Foo]
List(Foo(1), Foo(2), Foo(3)).map(_.makeClone())
That way instead of adding an identical method to each of your case classes, you make them extend the helper trait, which makes them cleaner and saves you some keystrokes.
On the other hand, the cloning would make no sense for immutable objects, so I infer your classes have mutable state. I would advise you to reconsider if you really cannot make them immutable, and use that type of cloning only at last resort. Immutability will protect yourself from a class of errors.
Why do you need to create identical copies of your case class instances? Case classes are, by default, immutable so can be safely shared.
In any case, I don't think you can do what you're asking with default parameters:
scala> trait Op { def copy():Op }
defined trait Op
scala> case class Op1(v:Int) extends Op
<console>:6: error: class Op1 needs to be abstract, since method copy in trait Op of type ()Op is not defined
case class Op1(v:Int) extends Op
The compiler doesn't create methods with all combinations of the optional parameters in the defining class. The default values are inserted in the place where the method is called.