Scala中的模式匹配与case类

4

我正在设计一个远程存储模型,最终得到的是:

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) => //
  }

但是Scala编译器会出现以下警告:
Warning:(39, 5) match may not be exhaustive.
It would fail on the following inputs: (GcsFile(_, _), S3Config(_)), (S3File(_, _), GcsConfig(_))
    (storageFile, storageConfig) match {

但是在我的情况下,使用GcsConfig打开S3File或者使用S3Config打开GcsFile显然是不合理的。有没有一种方法来增强模型?

我个人不喜欢在那些像GcsFileS3Config这样的不真实情况下抛出异常或将其保留为MatchError


StorageTag的作用是什么?我发现你根本没有使用它。 - Mahmoud Hanafy
3个回答

8
你需要向编译器提供一些关于允许哪些对的信息。通过将配对 storageFile: StorageFile[T], storageConfig: StorageConfig[T] 传递给 open 方法,你总是有风险让其他人使用错误的参数调用 open 方法,从而需要处理异常情况。为了以类型安全的方式使其正常工作,需要传递预定义的类型,该类型“知道”允许哪些对。
例如像这样:
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) => ???
  }

2
Scala编译器抱怨了,它是正确的,因为你没有覆盖所有的可能性。我认为你有两个选择。
1. 基于通用类型进行模式匹配(就像这个问题中所示:Scala中基于通用类型进行模式匹配
def open[T <: StorageTag](storageFile: StorageFile[T], storageConfig: StorageConfig[T]): OutputStream =
  (storageFile, storageConfig) match {
    case x if typeOf[T] <:< typeOf[Gcs]  => //
    case x if typeOf[T] <:< typeOf[S3]   => //
  }

最简单的方法是将逻辑提取到两个服务类中,如 @Bogdan Vakulenko 所述。


2

我想提出另一种解决这个问题的方法。
根据我对你模型的理解,你真正需要做的是确定要执行正确的open 逻辑的远程存储位置。
因此,你只需要提供隐式证据,例如像这样:

sealed trait StorageTag extends Product with Serializable
implicit case object Gcs extends StorageTag
implicit case object S3 extends StorageTag

sealed trait StorageFile[T <: StorageTag] extends Product with Serializable { 
  def bucket: String
  def path: String
}
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] extends Product with Serializable {
  def keyPath: String
}
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])
                         (implicit tag: T):String = tag match {
  case S3  =>
    s"S3 -> bucket: '${storageFile.bucket}', path: '${storageFile.path}' | config keyPath: '${storageConfig.keyPath}'"
  case Gcs =>
    s"Gcs -> bucket: '${storageFile.bucket}', path: '${storageFile.path}' | config keyPath: '${storageConfig.keyPath}'"
  }

现在,您可以这样调用方法。
open(S3File(bucket = "bucket", path = "path"), S3Config(keyPath = "keyPath"))
// res0: String = "S3 -> bucket: 'bucket', path: 'path' | config keyPath: 'keyPath'"

open(GcsFile(bucket = "bucket", path = "path"), GcsConfig(keyPath = "keyPath"))
// res1: String = "Gcs -> bucket: 'bucket', path: 'path' | config keyPath: 'keyPath'"

open(S3File(bucket = "bucket", path = "path"), GcsConfig(keyPath = "keyPath"))
// Compile time error!

请注意,这种方法仅在所有StorageFilesStorageConfigs具有相同属性的情况下才有效。如果不是这种情况,您可以尝试以下内容:
但请注意,此代码并非完全类型安全,可能会被欺骗
sealed trait StorageTag extends Product with Serializable
implicit case object Gcs extends StorageTag
implicit case object S3 extends StorageTag

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

sealed trait StorageConfig[T <: StorageTag] extends Product with Serializable
final case class GcsConfig(keyPath: String, name: 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])
                         (implicit tag: T): String = tag match {
  case S3 =>
    // These lines are not checked in compile-time, you can put GcsFile instead, and it will compile and fail at run-time!!!
    val S3File(bucket, path) = storageFile
    val S3Config(keyPath) = storageConfig
    s"S3 -> bucket: '${bucket}', path: '${path}' | config keyPath: '${keyPath}'"
  case Gcs =>
    val GcsFile(bucket, path, id) = storageFile
    val GcsConfig(keyPath, name) = storageConfig
    s"Gcs -> bucket: '${bucket}', path: '${path}', id: $id | config keyPath: '${keyPath}', name: 'name'"
}

open(S3File(bucket = "bucket", path = "path"), S3Config(keyPath = "keyPath"))
// res0: String = "S3 -> bucket: 'bucket', path: 'path' | config keyPath: 'keyPath'"

open(GcsFile(bucket = "bucket", path = "path", id = 0), GcsConfig(keyPath = "keyPath", name = "name"))
// res1: String = "Gcs -> bucket: 'bucket', path: 'path', id: 0 | config keyPath: 'keyPath', name: 'name'"

open(S3File(bucket = "bucket", path = "path"), GcsConfig(keyPath = "keyPath", name = "name"))
// Compile time error!

open(
  GcsFile(bucket = "bucket", path = "path", id = 0).asInstanceOf[StorageFile[StorageTag]],
  GcsConfig(keyPath = "keyPath", name = "name").asInstanceOf[StorageConfig[StorageTag]]
)(S3.asInstanceOf[StorageTag])
// Runtime error!!!!!!!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接