I have in my project objects that represent IDs.
Let's say it is ChairId, TableId, LampId. I want them all to inherit from GenericId. And I want to be able to call def f(x: GenericId) = x.id
I want them to hold only single id: String so I would like to make them extend AnyVal.
Also I would like for each type to provide function generate which would generate my specific ID i.e. I would like to type something like ChairId.generate()
I have typed this:
sealed abstract class GenericId(val id: String)
final case class ChairId(override val id: String) extends GenericId(id)
final case class TableId(override val id: String) extends GenericId(id
And I though if GenericId would inherit from AnyVal that would work but so far no luck ;/ I also tried making GenericId a trait and make case classes extend AnyVal with GenericId but also won't compile :/
Another thing with TableId.generate() I can provide companion object just with function generate and that basically solve my problem but I wondered if there is possibility to solve that without defining companion object? (i.e. through implicits somehow)
// edit
regarding comment to provide code which doesn't compile(and I would like to):
sealed abstract class AbstractId(val id: String) extends AnyVal
final case class CatId(override val id: String) extends AbstractId(id)
final case class DogId(override val id: String) extends AbstractId(id)
Value classes cannot work this way for a couple of reasons.
First, from the documentation, value classes cannot be extended by any other class, so AbstractId cannot extend AnyVal. (Limitation #7)
scala> abstract class AbstractId(val id: String) extends AnyVal
<console>:10: error: `abstract' modifier cannot be used with value classes
abstract class AbstractId(val id: String) extends AnyVal
^
Second, even if you make AbstractId a trait, and define the other ids like this:
final case class DogId(val id: String) extends AnyVal with AbstractId
.. the usage of the value class wouldn't fit your case, because the class itself would still get allocated. See the allocation summary:
A value class is actually instantiated when:
a value class is treated as another type.
a value class is assigned to an array.
doing runtime type tests, such as pattern matching.
Some quotes from the value classes SIP that are likely to clarify your doubts:
Value classes...
...must have only a primary constructor with exactly one public, val
parameter whose type is not a value class.
... cannot be extended by another class.
As per 1. it can not be abstract; per 2. your encoding doesn't work.
There is another caveat:
A value class can only extend universal traits and cannot be extended
itself. A universal trait is a trait that extends Any, only has defs
as members, and does no initialization. Universal traits allow basic
inheritance of methods for value classes, but they incur the overhead
of allocation.
With all that in mind, based on your last snippet, this might work:
sealed trait AbstractId extends Any { def id: String }
final case class CatId(id: String) extends AnyVal with AbstractId
final case class DogId(id: String) extends AnyVal with AbstractId
But keep in mind the allocation only occurs if you want to use CatId and DogId as an AbstractId. For better understanding I recommend reading the SIP.
Related
How to properly implement this in Scala?
case class WithPagingJson[T](meta: PagingMetaJson, objects: Array[T])
case class CandidatesListJson extends WithPagingJson[CandidateJson]
case class OpeningsListJson extends WithPagingJson[OpeningJson]
As you see, these classes are models for JSON data received from various APIs.
They all have same basic structure with two top-level properties for paging info and for array of requested objects.
Compiler tells me case to case inheritance is prohibited.
What is a proper Scala way of doing this?
If CandidatesJson really doesn't add anything to WithPagingJson, it can be a type alias:
type CandidatesJson = WithPagingJson[CandidateJson]
Otherwise, does WithPagingJson really need to be a case class? If you make it abstract and all its subtypes case classes, then you effectively still get all the equality, hash code, etc. benefits.
Would you mind showing the code for inheriting from abstract class?
abstract class WithPagingJson[T](val meta: PagingMetaJson, val objects: Array[T])
case class CandidatesJson(override val meta: PagingMetaJson, override val objects: Array[CandidateJson]) extends WithPagingJson[CandidateJson](meta, objects)
it seems I will have to exactly repeat whole parameters list for case class anyways? I actually was hoping to avoid exactly that.
If they are the same, yes, but they don't have to be. Maybe you want to make the base type a trait instead
trait class WithPagingJson[T] {
val meta: PagingMetaJson
val objects: Array[T]
}
case class CandidatesJson(meta: PagingMetaJson, objects: Array[CandidateJson]) extends WithPagingJson[CandidateJson]
To introduce more type safety, we can either use tagged type provided by shapeless or create a class which extends AnyVal. What are the differences and advantage/disadvantage to use one over the other?
Example:
trait CountryCodeTag
type CountryCode = String ## CountryCodeTag
class CountryCode(code: String) extends AnyVal
type CountryCode = String ## CountryCodeTag
+ String ## CountryCodeTag is a subtype of String, i.e. all methods from String can be used directly: countryCode.toUpperCase.
− String ## CountryCodeTag can be accidentally used where some String is expected, i.e. it's less type-safe.
− Creating new values is a little awkward: "a".asInstanceOf[String ## CountryCodeTag] or val tagger = new Tagger[CountryCodeTag]; tagger("a").
− Dependence on Shapeless (although this can be done manually).
class CountryCode(code: String) extends AnyVal
+ It's more type-safe.
− Methods from String are available with some extra efforts:
class CountryCode(val code: String) extends AnyVal
new CountryCode(countryCode.code.toUpperCase)
or
class CountryCode(val code: String) extends AnyVal
object CountryCode {
def unapply(...) = ...
}
countryCode match { case CountryCode(code) => new CountryCode(code.toUpperCase) }
or
case class CountryCode(code: String) extends AnyVal
countryCode.copy(code = countryCode.code.toUpperCase)
+ Creating new values is a little more natural: new CountryCode("a").
+ No extra dependencies (it's plain Scala).
These two methods also have different performance characteristics.
Tagged types, as opposed to value classes, prevent boxing even when their instances are used as e.g. elements of a list.
https://failex.blogspot.nl/2017/04/the-high-cost-of-anyval-subclasses.html
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]
}
please take a look at the following code:
scala> sealed abstract class Person(val name: String)
defined class Person
scala> case class Student(id: Int, name: String) extends Person(name)
<console>:8: error: overriding value name in class Person of type String;
value name needs `override' modifier
case class Student(id: Int, name: String) extends Person(name)
^
This might be a trivial question, but after searching the web for quite some time, I wasn't able to figure out how to simply pass the string that Student's constructor will be provided as name to the Person's constructor. I don't want to override anything. What am I doing wrong?
Thank you very much in advance!
All constructor parameters of a case class are vals. That's the whole point. Roughly speaking, it is what gives you the ability to enjoy the benefits cases classes provide compared to regular classes: copying, extraction, pattern matching, etc.
If you want Student to be a case class, you should override name. Theoretically, you can avoid overriding it by giving the val a different name: case class Student(id: Int, studentName: String) extends Person(studentName) - this works, but just doesn't make very much sense - you end up having two different member vals whose values are always identical.
Alternatively, if there is an actual reason why you don't want to override name (I can't imagine what one could possibly be, but if ...), then Student should not be a case class: class Student(val id: Int, name: String) extends Person(name).
I have a very specific scenario, in which I have some different abstract classes the have child case classes that can have different parameters, for example:
abstract class ball() {}
case class football(_name: String, _shape: String) extends ball
case class basketball(_name: String, _size: Int) extends ball
and a different abstract class:
abstract class food() {}
case class vegetarian(_name: String, calories: Int) extends food
case class meat(_name: String, proteinCount: Int) extends food
Now, the problem I'm facing is that I need to somehow extract the name of all of those without knowing what class it is, I just know that ALWAYS, EACH CLASS has a parameters named _name.
Supposing we have an object of any of above classes, I'm trying to do it like this:
object.getClass.getDeclaredField("_name").get(this)
But I'm getting the error:
can not access a member of class package.food with modifiers "private"
I tried putting val and var before parameters in class but it doesnt help. I also tried doing "setAccessible(true)" in a line before get(this), which also doesn't help.
The obvious clean solution would be to have a least a common trait to all these classes:
trait HasName {
def _name: String
}
and then you can safely do obj.asInstanceOf[HasName]._name. Better yet if you manage to keep around the static information that obj is a HasName, in which case obj._name suffices.
If you can't do any of that, reflection is the way to go. You can do it pretty easily using a structural type, in this case:
obj.asInstanceOf[{ def _name: String }]._name
Note that this will be slower than the above HasName solution, and completely unchecked at compile time.