how to get enums value using case classes in scala - scala

I am trying to implement enum using case class in scala.
with reference to question type parameter in traits to define data types in scala
sealed trait ElementType[+A] {
def length: Int
}
sealed trait Tag extends ElementType[Int] {
override def length: Int = 0
}
case object Tag100 extends Tag
sealed trait Value[+A] extends ElementType[A] {
override def length: Int = 0
}
final case class Value100(a: String) extends Value[String] {
override def length: Int = a.length
}
case class MessageId(tag: Tag, value: Value[String]){
}
case class MessageRepType(tag: Tag[Int], value:MessageType ){
}
sealed trait Message[T] {def typeCode: T;}
object MessageTypes {
sealed trait MessageType[Int] extends Message[Int]
case object TYPE1 extends MessageType[Int]{val typeCode = 0;}
case object TYPE2 extends MessageType[Int]{val typeCode = 1;}
}
case class ABCMessage (
id:MessageId,
messageType:MessageRepType)
I want to print a message with something like below id and type in new line
type value
100 abc
200 0
Explanation: 100 abc represents MessageId
200 0 represnets MessageRepType and O represent TYPE1

Assuming your "message" is a ABCMessage(MessageId(Tag100, Value100("abc"),MessageRepType(Tag200, TYPE1)) (also assuming Tag200 is similar to Tag100 and case class MessageRepType(tag: Tag[Int], value:MessageType) is case class MessageRepType(tag: Tag, value:MessageType) and sealed trait MessageType extends Message[Int]):
val sep = System.lineSeparator
def convertTag(tag: Tag): Int = tag match {
case Tag100 => 100
case Tag200 => 200
}
def convertMessageId(message: MessageId): String = {
val typ = convertTag(message.tag)
val value = message.value match {
case Value100(v) => v
}
s"$typ\t$value"
}
val id = convertMessageId(message.id)
def convertType(message: MessgeRepType): String = {
val typ = convertTag(message.tag)
val value = message.value match {// I would put typeCode to MessageType, but it is not there, so pattern matching
case TYPE1 => TYPE1.typeCode
case TYPE2 => TYPE2.typeCode
}
s"$typ\t$value"
}
val typ = convertType(message)
val message: ABCMessage = ABCMessage(MessageId(Tag100, Value100("abc"),MessageRepType(Tag200, TYPE1))
val messageAsString = Seq("type\tvalue", id, typ).mkString(sep)
println(messageAsString)

A different approach as still not sure what you want to achieve. The other answer with pattern matching is what I would prefer, more idiomatic.
You can use something like a simplified the visitor pattern (removed the irrelevant parts):
trait Show {
def show: String
}
sealed trait Tag extends Show {
override def show: String = this.getClass.getSimpleName.init
}
//object Tag {
case object Tag100 extends Tag
case object Tag200 extends Tag
//}
This way you can show the tags like this:
Tag100.show // Tag100
The case classes can also implement show:
sealed trait Value[+A] extends Show {
def v: String
override def show: String = v
}
case class Value100(v: String) extends Value[String]
You can use this in other methods too to implement the printing you want:
def convertMessageId(message: MessageId): String = {
val typ = message.tag.show
val value = message.value.show
s"$typ\t$value"
}
val id = convertMessageId(message.id)
def convertType(message: MessgeRepType): String = {
val typ = message.tag.show
val value = message.value.show
s"$typ\t$value"
}
val message: ABCMessage = ABCMessage(MessageId(Tag100, Value100("abc"),MessageRepType(Tag200, TYPE1))
val messageAsString = Seq("type\tvalue", id, typ).mkString(sep)
println(messageAsString)
If type safety and easy, generic usage is also important, take a look at Shapeless' offerings and this blog post.
If type safety is not so important, maybe for your use case it is enough to know that case classes implement Product, so you can visit them one-by-one and use its content.
Alternative solution might be to convert your datastructure to for example JSON (there are many Scala libs for that) and use that for further processing. Still generic, but not type-safe.
(This question might also be relevant.)

Related

Is there some way in scala that I can return a type?

I have a lot of classes such as DataFrameFlow, TextFlow, RDDFlow. They all derive from base class Flow.
Now I want to write a function judgeFlow which can read from a path: String and return something representing exact Flow type from which I can create corresponding instance. The whole code seems like the following
def judgeFlow(path:String) = /*1*/ {
Flow.getStoreType(path) match {
case StoreType.tdw =>
DataFrameFlow
case StoreType.hdfs =>
TextFlow
}
}
def createFlow(typeInfo:/*2*/) = /*3*/{
new typeInfo()
}
However, I don't know how to write in place 1, 2 and 3.
EDIT
Knowing how to construct them is not enough here, because I also want the following:
pattern matching through typeInfo
some ways to do asInstanceOf
EDIT 2
Definition of Flow
abstract class Flow(var outputName: String) extends Serializable{
def this() = this("")
...
}
Definition of DataFrameFlow
class DataFrameFlow(d: DataFrame, path: String) extends Flow {
var data: DataFrame = d
def this(data: DataFrame) = this(data, "")
def this(path: String) = this(null, path)
def this() = this(null, "")
...
}
Pattern matching can't return different types from different cases. The type returned by pattern matching is the least upper bound of types returned in cases.
When someone wants to return different types, most probably he/she wants a type class.
sealed abstract class Flow
class DataFrameFlow extends Flow
class TextFlow extends Flow
class RDDFlow extends Flow
trait JudgeFlow[In] {
type Out <: Flow
def judgeFlow(in: In): Out
}
object JudgeFlow {
implicit val `case1`: JudgeFlow[???] { type Out = DataFrameFlow } = ???
implicit val `case2`: JudgeFlow[???] { type Out = TextFlow } = ???
implicit val `case3`: JudgeFlow[???] { type Out = RDDFlow } = ???
}
def judgeFlow[In](in: In)(implicit jf: JudgeFlow[In]): jf.Out = jf.judgeFlow(in)
But the trouble is that types are resolved at compile time. You seem to want to choose a case based on a value of string i.e. at runtime. So you can't return more specific types than just Flow at compile time.
flatMap with Shapeless yield FlatMapper not found
It's hard to guess your use case completely.
But using Scala reflection you can try
import scala.reflect.runtime.universe._
import scala.reflect.runtime.currentMirror
def judgeFlow(path:String): Type = {
Flow.getStoreType(path) match {
case StoreType.tdw =>
typeOf[DataFrameFlow]
case StoreType.hdfs =>
typeOf[TextFlow]
}
}
def createFlow(typeInfo: Type): Flow = {
val constructorSymbol = typeInfo.decl(termNames.CONSTRUCTOR).asMethod
val classSymbol = typeInfo.typeSymbol.asClass
val classMirror = currentMirror.reflectClass(classSymbol)
val constructorMirror = classMirror.reflectConstructor(constructorSymbol)
constructorMirror().asInstanceOf[Flow]
}

Scala sealed trait - replicate Enumeration `withName` method

I'm trying to refactor an Enumeration to a sealed trait with concrete classes because I need to pack more functionality into them. With the sealed trait, I'd like to have functionality similar to Enumeration's withName(String) method. I've come up with the following code to do so and am looking for feedback:
sealed trait Foo {
def name: String
}
object Foo {
case object FooA extends Foo {
override val name: String = "a"
}
case object FooB extends Foo {
override val name: String = "b"
}
val values = Seq(FooA, FooB)
def withName(name: String): Option[Foo] = {
values.find(value => value.name.equals(name))
}
}
I can then use the withName(String) method to get the corresponding concrete object of type Foo as an Option:
val testFooAName = "a"
val testFooA = Foo.withName(testFooAName) // Will yield Some(FooA)
testFooA match {
case Some(Foo.FooA) => println("Matched Foo.FooA!")
case Some(Foo.FooB) => print("Matched Foo.FooB!")
}
val testFooNoneName = "none"
val testFooNone = Foo.withName(testFooNoneName) // Will yield None
Output:
Matched Foo.FooA!
Is this approach correct?
Yes, it looks fine
Minor simplification: Have a map for fast lookup
val values = Seq(FooA, FooB)
val fastLookup = values.map(v => v.name -> v).toMap
def withName(name: String): Option[Foo] = fastLookup.get(name)
There is a handy library by beachape which can be used to provide some of the functionality of enums to your sealed trait
the package you need to include in your sbt is:
"com.beachape" %% "enumeratum" % "1.5.15"
then extend your your object with the Enum like so:
import enumeratum._
sealed trait Foo
object Foo extends Enum[Foo] {
case object FooA extends Foo
case object FooB extends Foo
}
The you can use the withName function (amongst others) to get the sealed trait you want:
Foo.withName("FooA")

How can I extract shared Scala enum logic into a trait or superclass?

I need to define a bunch of similar enums:
object Color extends MyEnum[Color.Color] {
type Color = Value
val Red, Green, Periwinkle = Value
}
object Shape extends MyEnum[Shape.Shape] {
type Shape = Value
val Square, Circle, Balbis = Value
}
I'd like to extract shared functionality into an abstract class or trait, to support 1) apply and unapply methods, and 2) implicit conversions from enums to their string representations:
abstract class MyEnum[T] extends Enumeration {
implicit def valueToString(value: T): String = value.toString
implicit def stringToValue(string: String): T = apply(string)
def unapply(arg: String): Option[T] =
values.find(_.toString == arg).map(_.asInstanceOf[T])
def apply(arg: String): T =
values.find(_.toString == arg)
.getOrElse(sys.error(s"Invalid value '$arg'"))
.asInstanceOf[T]
}
This doesn't compile; when I add the type param in MyEnum[Color.Color], it breaks the Value type for some reason.
Is there some way to get what I want with this enum syntax, or do I need to switch to a case object solution (or something more complicated)?
This Blog discusses the Enumeration Hell in Scala: scala-enumerations-hell
The solution is not to use Scala's Enumeration type but create your own Enumeration class. Here is the abstract class from that blog:
abstract class Enum[Enum : ClassTag] { self =>
val values = {
import runtime.universe._
val mirror = runtimeMirror(self.getClass.getClassLoader)
val classSymbol = mirror.classSymbol(self.getClass)
classSymbol.toType.members
.filter(_.isModule)
.map(symbol => mirror.reflectModule(symbol.asModule).instance)
.collect { case v: Enum => v }
}
def forName(name: String): Option[Enum] = values.find(_.name == name)
implicit class RichardEnum(enum: Enum) {
def name: String = enum.toString
}
}
There is also a framework that provides that for you: lloydmeta/enumeratum

type parameter in traits to define data types in scala

I am new to scala and want to create a model class for a message .Message are comprisedoing of tag as key with value and value can be a string with specified length,int or a enumeration
tag value
100 a
200 b constraint of length =45
300 0,1,2
where 0-new
1-amend
2- cancel
trait ElementType {
type A
type length
}
case class Tag() extends ElementType {
override type A = this.type
}
case class Value() extends ElementType{
override type A = this.type
override type length = this.type
}
case class Message(
messageId:MessageId
)
case class MessageId(tag: Tag[Int], value: Value[String]){
override def toString = tag + "=" + value + 1.toChar
}
I am getting compile error in MessageId,is there a better way to write the code
I guess your problem with MessageId that it refers to a Tag[Int] and Value[String], which have no generic parameters.
Try something like this, though I think you will need to redefine the Tag and Value classes too:
case class MessageId(tag: Tag{type A = Int}, value: Value{type A = String}){
override def toString = tag + "=" + value + 1.toChar
}
As you seem to want to use them as generics, I would try something like this:
sealed trait ElementType[+A] {
def length: Int
}
sealed trait Tag extends ElementType[Int] {
override def length: Int = 0
}
case object Tag100 extends Tag
case object Tag200 extends Tag
case object Tag300 extends Tag
sealed trait Value[+A] extends ElementType[A] {
override def length: Int = 0
}
final case class Value100(a: String) extends Value[String] {
override def length: Int = a.length
}
final case class Value200(b: String) extends Value[String] {
require(b.length <= length)
override def length: Int = 45
}
case object Value300One extends Value[Nothing]
case object Value300Two extends Value[Nothing]
case object Value300Three extends Value[Nothing]
case class Message(
messageId:MessageId
)
case class MessageId(tag: Tag, value: Value[String]){
override def toString = tag + "=" + value + 1.toChar
}
+ in [+A] means the type will be covariant, so if Q is a subtype of R, ElementType[Q] is a subtype of ElementType[R]. (This is necessary for Tag to be compatible with any ElementType (so it extends the extreme ElementType[Nothing]).)
Your use case for length is not clear for me. If you want it to be a compiletime constraint, take alook at Shapeless' Sized and an example on its usage.

How do I create an enum in scala that has an extra field

In Java I have something like this
public enum FlatFileHeaderMapping {
HEADER_EL(1),
HEADER_RESERVED1(5),
HEADER_RESERVED2(2),
HEADER_MESSAGE_TYPE(4)
public final int fieldSize;
private FlatFileHeaderMapping(int fieldSize) {
this.fieldSize = fieldSize;
}
}
which I can then use it place each line into a map and later access the keys in the map via this enum (like symbols)
Enumeration does not have this quality as far as I can see, and case classes are not ordered like the enum declarations - so cannot be used to match a record layout as shown above. At least not without the support of an ordered collection.
I could be missing something obvious, hence the question!
Thanks
Ray
overthink is right, but there's a less verbose way of declaring the case objects:
sealed abstract class FlatFileHeaderMapping(val fieldSize: Int)
case object HEADER_EL extends FlatFileHeaderMapping(1)
case object HEADER_RESERVED1 extends FlatFileHeaderMapping(5)
case object HEADER_RESERVED2 extends FlatFileHeaderMapping(2)
case object HEADER_MESSAGE_TYPE extends FlatFileHeaderMapping(4)
You could try using case objects:
sealed trait FlatFileHeaderMapping { val fieldSize: Int }
case object HEADER_EL extends FlatFileHeaderMapping { val fieldSize = 1 }
case object HEADER_RESERVED1 extends FlatFileHeaderMapping { val fieldSize = 5 }
case object HEADER_RESERVED2 extends FlatFileHeaderMapping { val fieldSize = 2 }
case object HEADER_MESSAGE_TYPE extends FlatFileHeaderMapping { val fieldSize = 4 }
You can then use the enum like so:
object Test {
def foo(x: FlatFileHeaderMapping) {
val result =
x match {
case HEADER_EL => "it's a HEADER_EL!"
case other => "its field size is: " + other.fieldSize
}
println(result)
}
def main(args: Array[String]) {
foo(HEADER_EL)
foo(HEADER_MESSAGE_TYPE)
}
}
The main nicety you get here is compile-time checking that all enum values are handled. i.e in the x match { ... } code above you'd get a compile error if you didn't have the 'case other => ...` clause in there.
I'm pretty much just restating this answer, which lists pros and cons of this approach.
object Direction extends Enumeration {
val North = Value("North")
val East = Value("East")
val South = Value("South")
val West = Value("West")
}
scala> import Direction._
scala> values foreach println
scala> val map = HashMap(North -> 1, South -> 2)
This is answered in Enumeration with constructor and lookup table
A simpler solution exist for integer value:
object FlatFileHeaderMapping extends Enumeration {
type FlatFileHeaderMapping = Value
val HEADER_EL = Value(1, "HEADER_EL")
val HEADER_RESERVED1 = Value(5, "HEADER_RESERVED1")
val HEADER_RESERVED2 = Value(2, "HEADER_RESERVED2")
val HEADER_MESSAGE_TYPE = Value(4, "HEADER_MESSAGE_TYPE")
}
Reproducing the contents of the accepted answer, as it's hidden behind a broken Tumblr link (that I accessed via Archive.org), which in turn points to this page.
trait Enum { //DIY enum type
import java.util.concurrent.atomic.AtomicReference //Concurrency paranoia
type EnumVal <: Value //This is a type that needs to be found in the implementing class
private val _values = new AtomicReference(Vector[EnumVal]()) //Stores our enum values
//Adds an EnumVal to our storage, uses CCAS to make sure it's thread safe, returns the ordinal
private final def addEnumVal(newVal: EnumVal): Int = { import _values.{get, compareAndSet => CAS}
val oldVec = get
val newVec = oldVec :+ newVal
if((get eq oldVec) && CAS(oldVec, newVec)) newVec.indexWhere(_ eq newVal) else addEnumVal(newVal)
}
def values: Vector[EnumVal] = _values.get //Here you can get all the enums that exist for this type
//This is the trait that we need to extend our EnumVal type with, it does the book-keeping for us
protected trait Value { self: EnumVal => //Enforce that no one mixes in Value in a non-EnumVal type
final val ordinal = addEnumVal(this) //Adds the EnumVal and returns the ordinal
def name: String //All enum values should have a name
override def toString = name //And that name is used for the toString operation
override def equals(other: Any) = this eq other.asInstanceOf[AnyRef]
override def hashCode = 31 * (this.getClass.## + name.## + ordinal)
}
}
//And here's how to use it, if you want compiler exhaustiveness checking
object Foos extends Enum {
sealed trait EnumVal extends Value /*{ you can define your own methods etc here }*/
val F = new EnumVal { val name = "F" }
val X = new EnumVal { val name = "X" }
}
/**
scala> Foos.values.find(_.name == "F")
res3: Option[Foos.EnumVal] = Some(F)
scala> Foos.X.ordinal
res4: Int = 1
scala> def doSmth(foo: Foos.EnumVal) = foo match {
case Foos.X => println("pigdog")
}
<console>:10: warning: match is not exhaustive!
missing combination $anon$1
missing combination $anon$2
scala> def doSmth(foo: Foos.EnumVal) = foo match {
case Foos.X => println("pigdog")
case Foos.F => println("dogpig")
}
doSmth: (foo: Foos.EnumVal)Unit
**/
//But if you don't care about getting exhaustiveness warnings, you can do:
object Foos extends Enum {
case class EnumVal private[Foos](name: String) extends Value /* { you can define your own methods and stuff here } */
val F = EnumVal("F")
val X = EnumVal("X")
}
/**
Which is a bit less boilerplatey.
Cheers,
√
**/