Scala: Pattern matching a generic case class for "concrete" cases - scala

let's say I have:
sealed trait Data
final case class TextData() extends Data
final case class ImageData() extends Data
final case class MetaData[D <: Data](data: D) extends Data
I want to pattern match Data objects. I have defined:
def getDataTypeName(data: Data): String ={
data match {
case TextData()=>"text"
case ImageData()=>"image"
//MetaData(MetaData(_))=>"metaMeta1" //a
case _:MetaData[MetaData[_]]=>"metaMeta2" //b
//case MetaData(data:Data)=>"meta"+ getDataTypeName(data) //c
//MetaData(TextData(_))=>"metaMeta1" //d
case MetaData(_)=>"meta"
}
}
I want to be able to see if my data is a meta-metadata, meta-TextData, etc.
Line a give me an error, saying:
constructor cannot be instantiated to expected type; found :
inst$A$A.MetaData[D] required: A$A115.this.Data case
MetaData(MetaData(_))=>"metaMeta1"
I would prefer to have something resembling line a. alternatively something like line c would be nice too.
I also tried line b. This one generates some warnings:
non-variable type argument A$A115.this.MetaData[] in
type pattern A$A115.this.MetaData[A$A115.this.MetaData[]] is
unchecked since it is eliminated by erasure
case :MetaData[MetaData[]]=>"metaMeta"
^
I get this warning for both MetaData[_]s. My understanding is that the said pattern is reduced to _ after type erasure which is consistent with my test results.
I therefore want to know if there is a way of matching against MetaMetaData, MetaTextData ,MetaMetaMetaData etc.
Alternatively, is my design using sealed class in a way they shouldn't be used? What alternative design would you suggest?

I made a change to the MetaData() definition to make this work.
sealed trait Data
final case class TextData() extends Data
final case class ImageData() extends Data
final case class MetaData(data: Data) extends Data
def printType(data: Data): String = data match {
case TextData()=>"text"
case ImageData()=>"image"
case MetaData(d)=>"meta:" + printType(d)
}
val md = MetaData( MetaData(TextData()) )
printType(md) // res0: String = meta:meta:text

sealed trait Data
final case class TextData() extends Data
final case class ImageData() extends Data
final case class MetaData[D <: Data](data: D) extends Data
and
def getDataTypeName(data: Data): String ={
data match {
case TextData()=>"text"
case ImageData()=>"image"
case MetaData(MetaData(_))=> "metaMeta1"
case MetaData(TextData())=> "textMeta1"
case MetaData(ImageData())=> "imgMeta1"
}
}
testing on REPL:
scala> getDataTypeName(TextData())
res1: String = text
scala> getDataTypeName(MetaData(TextData()))
res2: String = textMeta1
scala> getDataTypeName(MetaData(MetaData(TextData())))
res3: String = metaMeta1

Related

Complex Sum Type in Scala

I have the following Type that i need to model:
sealed trait FieldType
case object INT extends FieldType
case object UINT extends FieldType
case object FLOAT extends FieldType
case object DOUBLE extends FieldType
case object BOOL extends FieldType
case object STRING extends FieldType
case object DATETIME extends FieldType
case class LIST(fieldType: FieldType) extends FieldType
case class SET(fieldType: FieldType) extends FieldType
The Issue i have is that in fact LIST and SET are actually ComplexFieldType that contain FieldType. In other words LIST Can Not Contain List or SET, same thing for SET.
What would be the proper way to model that, to ensure exhaustivity issue i.e. having the compiler telling me when i am missing some values.
I tried to introduce intermediary sealed trait as in SimpleFieldType and ComplexFieldType but then the pattern matching was messed up.
Only solution that i see if I don't want to compound things as in
case object LIST_INT
is to use smart constructor for LIST and SET.
However I thought drop a note and see what the scalaSphere knows about it.
It works indeed as such:
sealed trait FieldType
sealed trait SimpleFieldType extends FieldType
case object INT extends SimpleFieldType
case object UINT extends SimpleFieldType
case object FLOAT extends SimpleFieldType
case object DOUBLE extends SimpleFieldType
case object BOOL extends SimpleFieldType
case object STRING extends SimpleFieldType
case object DATETIME extends SimpleFieldType
sealed trait ComplexFieldType extends FieldType
case class LIST(fieldType: SimpleFieldType) extends ComplexFieldType
case class SET(fieldType: SimpleFieldType) extends ComplexFieldType
val field1: FieldType = INT
field1 match {
case FLOAT =>
case BOOL =>
case INT =>
case UINT =>
case DATETIME =>
case DOUBLE =>
case STRING =>
case LIST(_) =>
case SET(_) =>
}
ScalaC detect the non-exhaustiveness. I just had to use https://github.com/rtimush/sbt-rewarn#sbt-rewarn as the recompilation just skip it

Combine related ADTs in a type safe way

I'm designing a typesafe api to work with a "types" -- an abstraction in the application I'm working on. Here is how it looks like:
sealed trait EnumType
case object A extends EnumType
case object B extends EnumType
case object C extends EnumType
sealed abstract class TypeInfo[T <: EnumType](val enumType: T)
case class Ainfo() extends TypeInfo(A)
case class Binfo() extends TypeInfo(B)
case class Cinfo() extends TypeInfo(C)
sealed trait TypeMeta[T <: EnumType]
case class Ameta() extends TypeMeta[A.type]
case class Bmeta() extends TypeMeta[B.type]
case class Cmeta() extends TypeMeta[C.type]
case class TypeDescription[T <: EnumType](info: TypeInfo[T], meta: TypeMeta[T])
I'm confused about defining a function which would accept a List of TypeInfo and return TypeDescription. I currently implemeted it as follows:
//Type parameter with omitted bound? Is that type safe?
def toDescription(lst: List[TypeInfo[_]]): List[TypeDescription[_]] = {
lst map {
case a: Ainfo => TypeDescription(a, Ameta())
case b: Binfo => TypeDescription(b, Bmeta())
case c: Cinfo => TypeDescription(c, Cmeta())
}
}
To workaround the issue I used [_] pattern which does not look typesafely. Is there a way to redeclare the function?
Its type safe , however the 2 type parameters are bound individually to their own constraints and not to one another.
If you are looking to do that , I think you would need to define a method type parameter like so
//Type parameter with omitted bound? Is that type safe?
def toDescription[T<:EnumType](lst: List[TypeInfo[T]]): List[TypeDescription[T]] = {
lst map {
case a: Ainfo => TypeDescription(a, Ameta())
case b: Binfo => TypeDescription(b, Bmeta())
case c: Cinfo => TypeDescription(c, Cmeta())
}
}
Now if you tried to write
case a: Ainfo => TypeDescription(a, Bmeta())
you will get a compilation error

Exhaustive match on possible object values

First the code:
object MyEnums {
sealed abstract class MyEnum(val value: String)
case object First extends MyEnum("Some_ugly_looking_value1")
case object Second extends MyEnum("Some_ugly_looking_value2")
case object Third extends MyEnum("Some_ugly_looking_value3")
case object Fourth extends MyEnum("Some_ugly_looking_value4")
def fromString(value: String): Option[MyEnum] =
value match {
case First.value => Option(First)
case Second.value => Option(Second)
case Third.value => Option(Third)
case Fourth.value => Option(Fourth)
case _ => None
}
}
What I'm trying to achieve here is to be able to parse a string value coming from the outside into the form of the above enum. At the same time I would like to have the exhaustive pattern matching compiler warning if I don't cover all options in the match expression. What options do I have here? I don't like what I implemented above, since if this enum grows I may just forget to implement the new case clause...
Consider enumeratum like so
import enumeratum._
sealed abstract class MyEnum(override val entryName: String) extends EnumEntry
object MyEnum extends Enum[MyEnum] {
val values = findValues
case object First extends MyEnum("Some_ugly_looking_value1")
case object Second extends MyEnum("Some_ugly_looking_value2")
case object Third extends MyEnum("Some_ugly_looking_value3")
case object Fourth extends MyEnum("Some_ugly_looking_value4")
}
MyEnum.withName("Some_ugly_looking_value1") // res1: MyEnum = First
Now we do not have to fiddle with pattern match when adding a new case object.

How do I use the withName() method for an Enumeratum IntEnumEntry

I am currently using Enumeratum 1.5.13 in Scala 2.12.6. I have the following defined:
sealed abstract class PartOfSpeech(val value: Int) extends IntEnumEntry
case object PartOfSpeech extends IntEnum[PartOfSpeech] {
val values = findValues
case object Noun extends PartOfSpeech(0)
case object Adjective extends PartOfSpeech(1)
case object Verb extends PartOfSpeech(2)
case object Adverb extends PartOfSpeech(3)
case object Numeric extends PartOfSpeech(4)
case object Exclamation extends PartOfSpeech(5)
case object Preposition extends PartOfSpeech(6)
case object Pronoun extends PartOfSpeech(7)
case object Conjunction extends PartOfSpeech(8)
case object Determiner extends PartOfSpeech(9)
case object Article extends PartOfSpeech(10)
}
Then, when I attempt to use the withName() method like this:
val pos = PartOfSpeech.withName("Noun")
...I receive a compilation error indicating the method withName is not found. So, given I don't see a ScalaDoc for Enumeratum (at least I have not been able to find them), I don't know how to answer this without digging through its source code. Before investing time in that, I thought I might see if someone else has a simple and/or obvious solution.
What you are looking for was implemented on version 1.7.0 of Enumeratum.
you have 2 options to work with:
EnumEntry with "withName" function.
StringEnumEntry with "withValue" function.
In both ways you can get your needs
// Option 1:
sealed trait PartOfSpeech(val value: Int) extends EnumEntry
case object PartOfSpeech extends Enum[PartOfSpeech] {
val values = findValues
case object Noun extends PartOfSpeech(0)
case object Adjective extends PartOfSpeech(1)
case object Verb extends PartOfSpeech(2)
case object Adverb extends PartOfSpeech(3)
}
val pos = PartOfSpeech.withName("Noun") // return 0
// Option 2:
sealed trait PartOfSpeech(val value: String, val index: Int) extends StringEnumEntry
case object PartOfSpeech extends StringEnum[PartOfSpeech] {
val values = findValues
case object Noun extends PartOfSpeech("Noun", 0)
case object Adjective extends PartOfSpeech("Adjective", 1)
case object Verb extends PartOfSpeech("Verb", 2)
case object Adverb extends PartOfSpeech("Adverb", 3)
}
val pos = PartOfSpeech.withValue("None") // return 0
From what I can tell, the only way to make this work is for me to implement it myself within the case object. So, I added this:
private val enumEntryByClassSimpleNameLowerCase: Map[String, PartOfSpeech] =
values.map(enumEntry => (enumEntry.getClass.getSimpleName.takeWhile(_ != '$').toLowerCase, enumEntry)).toMap
def withName(value: String): Option[PartOfSpeech] =
enumEntryByClassSimpleNameLowerCase.get(value.toLowerCase)
It feels like a hack. I sure hope there's something inside of Enumeratum I am just missing. Please let me know if there is a better way to achieve this outcome. I use enums a whole bunch. So, this boilerplate is going to leak all over my codebase.

Scala pattern match is not exhaustive on nested case classes

I've got a case class hierarchy to encode some request and processing errors:
sealed trait OpError
sealed trait RequestErrorType
sealed trait ProcessingErrorType
final case class InvalidEndpoint(reason: String) extends RequestErrorType
final case class InvalidParameters(reason: String) extends RequestErrorType
final case class InvalidFormat(response: String) extends ProcessingErrorType
final case class EntityNotFound(id: Long) extends ProcessingErrorType
final case class RequestError(errorType: RequestErrorType) extends OpError
final case class ProcessingError(errorType: ProcessingErrorType) extends OpError
If I write a simple match across all patterns:
def printMatches(error: OpError): Unit = error match {
case RequestError(InvalidEndpoint(reason)) => //print something
case RequestError(InvalidParameters(reason)) => //print something
case ProcessingError(InvalidFormat(format)) => //print something
case ProcessingError(EntityNotFound(entityId)) => //print something
}
the compiler gives me a warning about missing match:
match may not be exhaustive.
It would fail on the following input: ProcessingError(_)
def printMatches(error: OpError): Unit = error match {
But ProcessingError takes in a ProcessingErrorType with only two extensions: InvalidFormat and EntityNotFound, both which are accounted for in the pattern match. What am I missing?
Even more curious is that if I change the parameter type of InvalidParameters or InvalidEndpoint to a String*, I don't get the error:
final case class InvalidParameters(reason: String*) extends RequestErrorType
Any ideas?
This is a confirmed bug. I filed a bug report for this and it has been since fixed for Scala 2.12.0-M4.
Very interesting! Unfortunately, I haven't found an answer. I've been revolving around http://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html#constructor-patterns but I haven't really found a valid explanation for what's going on.
Here's a simpler demo (hope you don't mind):
sealed abstract class ClassOne
case class ClassOneImpl() extends ClassOne
sealed abstract class ClassTwo()
case class ClassTwoImpl() extends ClassTwo
sealed abstract class Foo
case class FooOne(x: ClassOne) extends Foo
case class FooTwo(x: ClassTwo) extends Foo
def printMatches(st: Foo): Unit = st match {
case FooOne(ClassOneImpl()) => println()
case FooTwo(ClassTwoImpl()) => println()
}
I've observed that each of the following two modifications removes the warning:
1) Changing FooOne and FooTwo signatures so that instead of taking ClassOne and ClassTwo they take ClassOneImpl and ClassTwoImpl
2) Removing FooOne or FooTwo so that there's only one case class extending Foo (which leads to only one case in pattern matching).
Perhaps we could submit an issue and see what they say?
You can help the compiler with an unchecked annotation:
... = (error: #unchecked) match ...
but you should be sure, your match is exhaustive.
I think exhaustiveness matching works on a single inheritance level.
RequestErrorType and ProcessingErrorType are part of the constructor where that exhaustiveness is not checked.
You can see it from the reading of the code, but it seem that compiler does not.