Scala pattern match is not exhaustive on nested case classes - scala

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.

Related

How to combine ADTs in Scala?

I have two layers in my app: domain and application. Each layer has its own "error" ADT. For instance:
package com.domain.person
sealed trait DomainError
case object NoPermission extends DomainError
final case class Person(hasPermission: Boolean): Either[DomainError, ???] {
def doSomething() = {
if (!hasPermission)
Left(NoPermission)
else
...
}
}
and in my application layer (another package):
package com.application.person
sealed trait ApplicationError
case object PersonNotFound extends ApplicationError
case object UnexpectedFatalError extends ApplicationError
// and a function f :: Either ApplicationError Something
The issue is, since DomainError lives in another package, I can't just simply extend my ApplicationError trait:
sealed trait ApplicationError extends DomainError // compilation error
I could create yet another case object to wrap DomainError:
sealed trait ApplicationError
// n list of errors, and then:
final case class WrappedDomainError(d: DomainError) extends ApplicationError
but that solution is suboptimal at best.
And also, what if I want to be more specific in my doSomething() and, instead of returning a whole DomainError, a different subset?
doSomething :: Either DoSomethingErrors ???
I would have to account for all cases in each of my domain layer's functions.
Is there any way I can do a proper sum type in Scala?
Thanks
Wrapping your domain error in application error is not a bad idea, TBH. It's what I would've done in your situation. A few more options to consider:
make your DomainError and ApplicationError extends a common supertype Error, CommonError, Failure, etc. My personal preference is to extend Throwable - this way your error ASTs can become isomorphic to exceptions which can come in handy for Java interop reasons.
error channel also being composed of unions. Your final type will look somewhat like Either[ApplicationError Either DomainError, A]. It's a bit mouthful but you can make it look less ugly by introducing aliases.
type Result[+A] = Either[ApplicationError Either DomainError, A]
def doSomething: Result[???]
replace either with your own AST or use scalaz or other library's alternatives to Either3
sealed trait Result[+A]
case class Success[A](a: A) extends Result[A]
case class ApplicationErr(err: ApplicationError) extends Result[Nothing]
case class DomainErr[A](err: DomainErr) extends Result[Nothing]
def doSomething: Result[???]
Interpret DomainErrors into ApplicationErrors
val maybeDomainErrorVal: Either[DomainError, ???] = ???
val maybeApplicationErrorVal: Either[ApplicationError, ???] =
maybeDomainErrorVal.leftMap {
case NoPermission => UnexpectedFatalError
}

How should I implement scala interpreter for tuple?

I'm implementing scala interpreter and I have some problem.
To begin with, I want to implement tuple class
In my Value.scala, followed class exist:
case class TupleV(values: List[Type]) extends Value
Also, in my Expr.scala, followed class exist
case class TupleE(expressions: List[Type] extends Expr
Type is also in Expr.scala as followed:
sealed trait Type
case object IntT extends Type
case object BooleanT extends Type
case object TupleT extends Type
case object ListT extends Type
case object FunctionT extends Type
So, I implemented as followed:
case TupleE(values)=>TupleV(values)
but it says List[Expr] found, List[Value] is required. What is the problem in my code? what should I to to fix it?
Also I tried to implement in different way using ConsE and ConsV class which enable me to divide list into head and tail:
case class ConsE(head: Expr, tail: Expr) extends Expr//in Expr.scala
case class ConsV(head: Value, tail: Value) extends Value//in Value.scala
case ConsE(head, tail)=>ConsV(interp(head), interp(tail)//my implementation for ConsV interpreter&works well now
Using that, I tried
case TupleE(expression)=>expression match{
case ConsE(head, tail)=>ConsV(head, tail)
}
But it returns: "constructor cannot be instantiated to expected type" error for ConsE. How should I fix it?
Try to replace
case class TupleV(values: List[Type]) extends Value
case class TupleE(expressions: List[Type]) extends Expr
with
case class TupleV(values: List[Value]) extends Value
case class TupleE(expressions: List[Expr]) extends Expr
(Are those definitions given to you in an exercise or are they your own definitions that you can modify?)
Based on
case TupleE(values)=>TupleV(values)
you're writing interpreter
def interpret(expr: Expr): Value
i.e. continue what you started in How should I implement "add" interpreter in scala?
Then the definitions of TupleV, TupleE should be latter above. Former their definitions above make less sense to me.
Type is needed when you typecheck an Expr
def typecheck(expr: Expr): Type // or Option[Type]
Then you'll have one more hierarchy
case class TupleT(types: List[Type]) extends Type
So, I implemented as followed:
case TupleE(values)=>TupleV(values)
but it says List[Expr] found, List[Value] is required. What is the
problem in my code? what should I to to fix it?
This is irreproducible. The code compiles
https://scastie.scala-lang.org/KWlyFOYDRHOgN6UibVY1pw
Using that, I tried
case TupleE(expression)=>expression match{
case ConsE(head, tail)=>ConsV(head, tail)
}
But it returns: "constructor cannot be instantiated to expected type"
error for ConsE.
This error is clear: in TupleE(expression) expression has type List[Type], it can't match ConsE, it can match only ordinary scala List.

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 matching a generic case class for "concrete" cases

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