Scala case classes essentially capture a set of fields with helping methods.
How are case classes resolved? Are they expanded to different classes with fields or a generic class that contains, say, a HashMap<String, Field>?
If it's the latter, are case classes with single field more expensive than explicitly defined data objects?
Case classes are exactly the same as regular classes, except that they offer some additional convenience functions.
No, they are not backed by a map. What made you think that they are?
Related
I have been using sealed traits and case objects to define enumerated types in Scala and I recently came across another approach to extend the Enumeration class in Scala like this below:
object CertificateStatusEnum extends Enumeration {
val Accepted, SignatureError, CertificateExpired, CertificateRevoked, NoCertificateAvailable, CertChainError, ContractCancelled = Value
}
against doing something like this:
sealed trait CertificateStatus
object CertificateStatus extends {
case object Accepted extends CertificateStatus
case object SignatureError extends CertificateStatus
case object CertificateExpired extends CertificateStatus
case object CertificateRevoked extends CertificateStatus
case object NoCertificateAvailable extends CertificateStatus
case object CertChainError extends CertificateStatus
case object ContractCancelled extends CertificateStatus
}
What is considered a good approach?
They both get the job done for simple purposes, but in terms of best practice, the use of sealed traits + case objects is more flexible.
The story behind is that since Scala came with everything Java had, so Java had enumerations and Scala had to put them there for interoperability reasons. But Scala does not need them, because it supports ADTs (algebraic data types) so it can generate enumeration in a functional way like the one you just saw.
You'll encounter certain limitations with the normal Enumeration class:
the inability of the compiler to detect pattern matches exhaustively
it's actually harder to extend the elements to hold more data besides the String name and the Int id, because Value is final.
at runtime, all enums have the same type because of type erasure, so limited type level programming - for example, you can't have overloaded methods.
when you did object CertificateStatusEnum extends Enumeration your enumerations will not be defined as CertificateStatusEnum type, but as CertificateStatusEnum.Value - so you have to use some type aliases to fix that. The problem with this is the type of your companion will still be CertificateStatusEnum.Value.type so you'll end up doing multiple aliases to fix that, and have a rather confusing enumeration.
On the other hand, the algebraic data type comes as a type-safe alternative where you specify the shape of each element and to encode the enumeration you just need sum types which are expressed exactly using sealed traits (or abstract classes) and case objects.
These solve the limitations of the Enumeration class, but you'll encounter some other (minor) drawbacks, though these are not that limiting:
case objects won't have a default order - so if you need one, you'll have to add your id as an attribute in the sealed trait and provide an ordering method.
a somewhat problematic issue is that even though case objects are serializable, if you need to deserialize your enumeration, there is no easy way to deserialize a case object from its enumeration name. You will most probably need to write a custom deserializer.
you can't iterate over them by default as you could using Enumeration. But it's not a very common use case. Nevertheless, it can be easily achieved, e.g. :
object CertificateStatus extends {
val values: Seq[CertificateStatus] = Seq(
Accepted,
SignatureError,
CertificateExpired,
CertificateRevoked,
NoCertificateAvailable,
CertChainError,
ContractCancelled
)
// rest of the code
}
In practice, there's nothing that you can do with Enumeration that you can't do with sealed trait + case objects. So the former went out of people's preferences, in favor of the latter.
This comparison only concerns Scala 2.
In Scala 3, they unified ADTs and their generalized versions (GADTs) with enums under a new powerful syntax, effectively giving you everything you need. So you'll have every reason to use them. As Gael mentioned, they became first-class entities.
It depends on what you want from enum.
In the first case, you implicitly have an order on items (accessed by id property). Reordering has consequences.
I'd prefer 'case object', in some cases enum item could have extra info in the constructor (like, Color with RGB, not just name).
Also, I'd recommend https://index.scala-lang.org/mrvisser/sealerate or similar libraries. That allows iterating over all elements.
case classes have some nice percs, like copy, hashCode, toString, Pattern Matching. Why not make every Scala class a case class?
A case class is extremely good to hold complex values, like entity objects. They are thought precisely for that case, so they provide you methods that make sense precisely for this use case by synthesizing the methods you mentioned and furthermore making your class Serializable and creating a companion object with a "factory" method (other than the extractor for pattern matching).
The drawbacks are the following:
some of the properties that a case class has may not be interesting for the class you're creating: would you want an equals method on an object holding a database connection? Would it make sense for it to be Serializable? And if it did, would it be secure?
all these features are not free: they require the compiler to do some extra work and add to your final artifact size; why having these if you don't need the extra features a case class provides?
you cannot inherit from case class to another case class, which may go against how you are modeling your domain. Why? Short answer: equality. You can find a longer answer here.
Case classes have clear semantics -- data container (much better POJOs or ADT blocks, depends on your background).
Sometimes methods like copy or unapply can have confusing meaning -- e.g. if fields are mutable. Case classes are designed to be used in "idiomatic scala style", that might not be applicable everywhere.
Last but not the least -- technical disadvantages (more code in .class, more code to serialize, issues with inheritance).
It seems to me that I can make just about anything using object, trait, abstract class and in rare occasions, case class. Most of this is in the form object extends trait. So, I'm wondering, when should I, if ever, use a plain, standard class?
This is not a right place to ask this question
Looks like you are new Scala
Class is a specification for something(some entity) you want to model . It contains behavior and state
There is only one way to declare so called regular class using keyword class
Both trait and abstract class are used for inheritance.
trait is used for inheritance (generally to put common behavior in there). trait is akin to interface in Java. multiple inheritance possible with traits but not abstract class.
A class can extends one class or abstract class but can mixin any number of traits. Traits can have behavior and state.
case class is a nothing but a class but compiler produces some boilerplate code for us to make things easy and look good.
object is used when you want to declare some class but you want to have single instance of the class in the JVM (remember singleton pattern).
If an object performs stateful computations on its members i.e. its members are declared with vars;
Or, even if its member are only declared with vals but those vals store mutable data structures which can be edited in place, then it should be an ordinary (mutable) class akin to a Java mutable object.
The idiomatic way of using Case classes in Scala is as immutable types i.e. all the constructor arguments are vals. We could use vars but then we lose the advantages of case classes like equality comparisons will break over time.
Some advise from Programming in Scala by Odersky et al on deciding between using traits, abstract classes and concrete classes:
If the behavior will not be reused, then make it a concrete class. It is not reusable behavior after all.
If it might be reused in multiple, unrelated classes, make it a trait.
Only traits can be mixed into different parts of the class hierarchy.
If you want to inherit from it in Java code, use an abstract class.
Since traits with code do not have a close Java analog, it tends to be
awkward to inherit from a trait in a Java class. Inheriting from a
Scala class, meanwhile, is exactly like inheriting from a Java class.
As one exception, a Scala trait with only abstract members translates
directly to a Java interface, so you should feel free to define such
traits even if you expect Java code to inherit from it. See Chapter 29
for more information on working with Java and Scala together.
If you plan to distribute it in compiled form, and you expect outside
groups to write classes inheriting from it, you might lean towards
using an abstract class. The issue is that when a trait gains or loses
a member, any classes that inherit from it must be recompiled, even if
they have not changed. If outside clients will only call into the
behavior, instead of inheriting from it, then using a trait is fine.
If efficiency is very important, lean towards using a class. Most Java
runtimes make a virtual method invocation of a class member a faster
operation than an interface method invocation. Traits get compiled to
interfaces and therefore may pay a slight performance overhead.
However, you should make this choice only if you know that the trait
in question constitutes a performance bottleneck and have evidence
that using a class instead actually solves the problem.
If you still do not know, after considering the above, then start by
making it as a trait. You can always
change it later, and in general using a trait keeps more options open.
Consider the following toy class:
class myGiantClass(){
val serializableElement = ...
// lots of other variables and methods here
}
// main program
val listOfGiantObjects: List[myGiantClass] = ....
What I need is to serialize/deserialize listOfGiantObjects. The issue is that myGiantClass contains lots of junk objects and variables which I don't/can't serialize/deserialize. Instead the only element of the myGiantClass that I want to serialize is serializableElement inside each object of listOfGiantObjects.
So after deserialize, listOfGiantObjects is expected to contain a bunch of myGiantClass objects which contain only serializableElement (the rest set to default).
Any ideas?
Of course there are two approaches (or defaults): all elements should be serialized by default, or none.
Within the "all" scenario, you could take a look at the #transient annotation, for marking fields that should not be serialized.
It may seem an unoptimal approach in case of a large number of elements that should not be serialized. However, it does communicate what you are trying to achieve. Moreover, you could arrange your code using composition or inner classes to better define the scope of serialization.
At last resort, ad-hoc serializaion with custom attributes is a way (e.g., to implement the none-by-default scenario).
I'm defining a Scala class today, and I think "I need an equals method and a hashCode method; and a copy method would be handy too. I'll turn this into a case class." My class already has a bunch of other code, and is in no way trivial.
So fine, it all works and everything, but when the text books deal with case classes, all of the examples define them for use as value classes or 'data transfer objects'. Is it appropriate to define a non-trivial case class? Is the thought process described above OK, or do I need to think of case classes differently?
A case class provides, equals, hashCode and toString methods based on the main constructor parameters, all of which are turned into val too. In addition, the object companion gets an apply and an unapply methods, again based on the main constructor parameters.
Also, a case class inherits from Serializable and from Product, and should not be extended by other classes.
If all of these things are appropriate for your class, then feel free to declare it as a `case class'.
Feel free, provided it doesn't have descendants. Extending case classes is a bad idea.