Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern matching in Scala with case classes

I'm designing a model for remote storages and ended up with:

sealed trait StorageTag
case object Gcs extends StorageTag
case object S3 extends StorageTag

sealed trait StorageFile[T <: StorageTag]
final case class GcsFile(bucket: String, path: String) extends StorageFile[Gcs.type]
final case class S3File(bucket: String, path: String) extends StorageFile[S3.type]

sealed trait StorageConfig[T <: StorageTag]
final case class GcsConfig(keyPath: String) extends StorageConfig[Gcs.type]
final case class S3Config(keyPath: String) extends StorageConfig[S3.type]

def open[T <: StorageTag](storageFile: StorageFile[T], storageConfig: StorageConfig[T]): OutputStream =
  (storageFile, storageConfig) match {
    case (f: S3File, c: S3Config) => //
    case (f: GcsFile, c: GcsConfig) => //
  }

But Scala compiler complains with the following warning:

Warning:(39, 5) match may not be exhaustive.
It would fail on the following inputs: (GcsFile(_, _), S3Config(_)), (S3File(_, _), GcsConfig(_))
    (storageFile, storageConfig) match {

But in my specific case it is obviously a non-sense to open S3File with GcsConfig and vice versa. Is there a way to enhance the model?

I personally don't like the idea of throwing exception or leaving it as MatchError in those unreal cases like GcsFile with S3Config.

like image 837
Some Name Avatar asked Dec 13 '22 13:12

Some Name


1 Answers

You need to give to compiler some information about what pairs are allowed. By passing pair storageFile: StorageFile[T], storageConfig: StorageConfig[T] to open method you are always in risk that someone calls open method with a wrong par and you will have to handle the exceptional case. To make it works in a type-safe way, you need to pass predefined type that "knows" what pairs are allowed.

For example like this:

sealed trait StorageTag
case object Gcs extends StorageTag
case object S3 extends StorageTag

sealed trait StorageFile[T <: StorageTag]
final case class GcsFile(bucket: String, path: String) extends StorageFile[Gcs.type]
final case class S3File(bucket: String, path: String) extends StorageFile[S3.type]

sealed trait StorageConfig[T <: StorageTag]
final case class GcsConfig(keyPath: String) extends StorageConfig[Gcs.type]
final case class S3Config(keyPath: String) extends StorageConfig[S3.type]

sealed trait FileConfPair
case class S3Conf(f: S3File, c: S3Config) extends FileConfPair
case class ScsConf(f: GcsFile, c: GcsConfig) extends FileConfPair

def open[T <: StorageTag](fp: FileConfPair): OutputStream =
  fp match {
    case S3Conf(f: S3File, c: S3Config) => ???
    case ScsConf(f: GcsFile, c: GcsConfig) => ???
  }
like image 98
Bogdan Vakulenko Avatar answered Jan 12 '23 04:01

Bogdan Vakulenko