Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Surjectivity check when return type is sealed

Scala can warn when pattern match on a sealed type is not exhaustive, however can we check that a function returns all cases when the return type is sealed? For example, consider the following ADT

sealed trait Foo
case object Bar extends Foo
case object Qux extends Foo

Then function f: Foo => String on the algebraic data type Foo

def f(x: Foo): String = x match {
  case Bar => "bar"
}

raises warning

match may not be exhaustive.
It would fail on the following input: Qux
def f(x: Foo) = x match {

Is it possible to raise a similar non-exhaustion warning when return type is an ADT such as in the following implementation of f: String => Foo:

def f(x: String): Foo = x match {
  case "bar" => Bar
  // warn because we never return Qux 
}
like image 866
Mario Galic Avatar asked May 15 '19 14:05

Mario Galic


2 Answers

Maybe this is not really an answer, but it's too long for a comment anyway.

Pattern matching and function return values are two different things. Former operates on the type level, latter on the value level. When you pattern match on Bar, you're pattern matching on the type (just like e.g. Int). But when you return Bar, you're returning the case object value (just like e.g. 42).

Surjective function is defined as:

For every member y of the codomain, there exists at least one member x of the domain, such that f(x) = y.

Now it's easy to see why this check is not feasible / possible. What if your Bar wasn't a case object, but a class? E.g.

final case class Bar(name: String, surname: String, age: Int)

You would need to expect every possible value of Bar to be used (e.g. name = "John", surname = "Smith", age = 42).

Of course, this is not what you intended; what you described is a scenario where each subtype has exactly one inhabitant, because Bar and Qux are basically enums, and I can see why such a check might make sense to you. But it would have to be implemented for the general case of arbitrary number of inhabitants per (sub)type - it would need to verify that codomain contains at least one value of type Bar, at least one value of type Qux etc. which doesn't sound very useful.

As I said, this is not really an answer, but I wanted to give you an insight into what exactly it is that you're asking. :) Perhaps someone wrote something with reflection and/or macros which could provide such a check, but not to my knowledge. Hopefully with Scala 3 enums you will never need to write such a function anyway.

like image 76
slouc Avatar answered Nov 07 '22 06:11

slouc


Here are enumeration examples suggested by @LuisMiguelMejíaSuárez and @slouc which do provide case exhaustion:

enumeratum

import enumeratum._

sealed trait Foo extends EnumEntry
object Foo extends Enum[Foo] {
  val values = findValues
  case object Bar extends Foo
  case object Qux extends Foo  
}

Foo.withName("Qux")

Scala 3 Enumerations

enum Foo {
  case Bar
  case Qux
}

Foo.enumValueNamed("Qux"))

Both methods worked fine even with parameterised sealed type.

like image 2
Mario Galic Avatar answered Nov 07 '22 06:11

Mario Galic