Make a trait require an unspecified Enumeration as part of its interface - scala

How can I define a trait which defines that my case class should contain an Enumeration of some kind, when I don't want it locked to one specific Enumeration? See the explanation for more details:
I have some data that has three integer fields per entry, and each integer increases the specificity of the data.
The first field is the overall type of the data, like what industry is it from (e.g. baking), the second field is what kind of product it is (e.g. cake), and the third field is the type of product (e.g. strawberry pie).
I'm trying to map this data like so:
Each industry has its own case class
case class BakingProduct(...)
Each case class has an Enumeration in it.
case object BakingType extends Enumeration { type Product = Value, ... }
case class BakingProduct(primary: BakingType.Product, ...)
Each case class also holds an Int for the final field (it's not important enough for me to make an Enumeration for it)
case class BakingProduct(primary: BakingType.Product, secondary: Int)
To me this strikes a good balance between readability and writability, as opposed to creating case classes for every single combination, but my issue is that I want to create a trait that can encompass these Product classes; something like this:
trait ProductType {
val primary: Enumeration,
val secondary: Int
}
such that
case class BakingProduct(primary: BakingType.Product, secondary: Int) extends ProductType
is valid, but without locking primary to a specific type of Enumeration, such that I can make an Enumeration for each specific ProductType I want to use.
Should I try to make a parent Enumeration to encompass those other Enumerations (and if so, how? I haven't been able to figure that out)? I would also like to avoid making the trait generic if possible, so I don't have to give type parameters whenever I'm dealing with one. But if there's a nice way to do it with generics then that would be welcome as well, along with other suggestions :)

Related

Using Enumerations in Scala Best Practices

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.

Is there a difference between extending a trait with a type or using a type parameter in your case class?

I recently had a coworker implement a trait like
trait CaseClassStuff{
type T
val value: T
}
and then used it to instantiate case classes as
case class MyCaseClassString(value: String) extends CaseClassStuff { type T = String }
case class MyCaseClassDouble(value: Double) extends CaseClassStuff { type T = Double }
and I thought that was particularly whacky since it seemed reasonable enough to just do
case class MyCaseClass[T](value: T)
to get the exact same result. There was argument over how using the trait allowed us to avoid needing to update anything using that case class, since with the trait we just explicitly used MyCaseClassString and MyCaseClassDouble in different areas, but I wasn't sure how since they seemed to be ostensibly the same thing, especially since the only change between the two is their type. The program using them was set up to parse out the logic when it was a double or a string received.
So, my question is about whether or not they are different as far as the compiler is concerned, and whether or not there is actual benefit from doing it the way with the trait in general, or if it was just specific to my situation. It wasn't clear to either of us if it was best practice to use the trait or just the type parameter, since it seems like two ways to accomplish the same outcome.

Does the AnyVal with AnyRef as a parameter make sense in Scala?

Does below construct makes any sense? Are any benefits of using it?
final case class Id(uuid: UUID) extends AnyVal
As I understand above construct, Id doesn't have to be instantiated in some scenarios described here. But I have some doubts because I didn't find any example with AnyRef as a parameter.
Yes, this example makes sense. Extending AnyVal is useful when you want specific semantics for a type, but don't want to pay the additional allocation cost that come along with it. For example, say you have a typeclass instance for outputting string representation of values, like Show[A], and you want to give specific semantics to UUID, but there already exists an instance of Show[UUID] in scope which you can't control, this is when wrapping a type and introducing an implicit typeclass for it can be useful.
Do note that AnyVal may end up allocating instances of the wrapper class in specific cases as mentioned in this documentation:
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.

Enforce values a class member can be assigned : Scala

I intend to restrict the values the string member of my class can take. For eg: for a member named country, only allowable values should be the countries that i have defined like 'US', UK, CN, etc. This constants need to be string constants.
I thought of using Enum, but that does not give me flexibility with the String constants. Or may be i am not aware of how to use it.
Any suggestions?
What is the problem with the enum? Seems like a good fit for what you need. Here is an example:
object Country extends Enumeration {
val US = Value("United States")
val UK = Value("United Kingdom")
}
case class MemberCountry(country: Country.Value)
//example instantiation
MemberCountry(Country.UK)
MemberCountry(Country.withName("United States"))
Enum support is not that good in Scala. First, scala's built-in Enumeration is crap, don't use it.
The common approach for enumerations is sealed trait/class:
sealed abstract class Country(val name:String)
object Country {
case object US extends Country("US")
case object UK extends Country("UK")
case object CN extends Country("CN")
val values = Set(US, UK, CN)
}
Country.values.find(_.name == "US")
It has one problem, if you want the list of all possible values, you need to enumerate them yourself (the values field in the example above).
However, there are alternatives that solve that problem.
You can go with macro (check this thread).
Or use third-party library.
Enumeration has its strengths and weaknesses in Scala.
You could use any approach highlighted above namely:
Enumeration subclass
Use Sealed Trait/Class.
Both approaches have benefits and potential tradeoffs. It might be worth reading through this informative article on this issue:
http://underscore.io/blog/posts/2014/09/03/enumerations.html

Getting field name and types from Case Class (with Option)

Assuming we have a model of something, represented as a case class, as so
case class User(firstName:String,lastName:String,age:Int,planet:Option[Planet])
sealed abstract class Planet
case object Earth extends Planet
case object Mars extends Planet
case object Venus extends Planet
Essentially, either by use of reflection, or Macros, to be able to get the field names of the User case class, as well as the types represented by the fields. This also includes Option, i.e. in the example provided, need to be able to differentiate between an Option[Planet] and just a Planet
In scala'ish pseudocode, something like this
val someMap = createTypedMap[User] // Assume createTypedMap is some function which returns map of Strings to Types
someMap.foreach{case(fieldName,someType) {
val statement = someType match {
case String => s"$fieldName happened to be a string"
case Int => s"$fieldName happened to be an integer"
case Planet => s"$fieldName happened to be a planet"
case Option[Planet] => s"$fieldName happened to be an optional planet"
case _ => s"unknown type for $fieldName"
}
println(statement)
}
I am currently aware that you can't do stuff like case Option[Planet], since it gets erased by Scala's erasure, however even when using TypeTags, I am unable to wrote code that does what I am trying to do, and possibly deal with other types (like Either[SomeError,String]).
Currently we are using the latest version of Scala (2.11.2) so any solution that uses TypeTags or ClassTags or macros would be more than enough.
Option is a type-parametrized type (Option[T]). At runtime, unless you have structured your code to use type tags, you have no mean to distinguish between an Option[String] and an Option[Int], due to type erasure (this is true for all type-parametrized types).
Nonetheless, you can discriminate between an Option[*] and a Planet. Just keep in mind the first issue.
Through reflection, getting all the "things" inside a class is easy. For example, say you only want the getters (you can put other types of filters, there are A LOT of them, and not all behave as expected when inheritance is part of the process, so you'll need to experiment a little):
import reflect.runtime.{universe=>ru}
val fieldSymbols = ru.typeOf[User].members.collect{
case m: ru.MethodSymbol if m.isGetter => m
}
Another option you'd have, if you are calling the code on instances rather than on classes, is to go through every method, call the method and assign the result to a variable, and then test the type of the variable. This assumes that you are only calling methods that don't alter the state of the instance.
You have a lot of options, time for you to find the best one for your needs.