【问题标题】:Pattern matching in Scala with case classesScala中的模式匹配与案例类
【发布时间】:2018-12-08 08:02:08
【问题描述】:

我正在为远程存储设计一个模型,结果是:

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 显然是没有意义的,反之亦然。有没有办法增强模型?

我个人不喜欢在GcsFileS3Config 等不真实的情况下抛出异常或将其保留为MatchError 的想法。

【问题讨论】:

  • StorageTag 有什么用?我看到你根本没有使用它。

标签: scala pattern-matching case-class


【解决方案1】:

您需要向编译器提供一些关于允许哪些对的信息。通过将 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 基于泛型类型的模式匹配(如本题:Pattern matching on generic type in 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]   => //
      }
    

    2 最简单的方法是将逻辑提取到 2 个服务类作为点@Bogdan Vakulenko

    【讨论】:

      【解决方案3】:

      我想提出另一种解决此问题的方法。
      根据我对您模型的理解,您真正需要的是确定您必须执行正确的远程存储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!!!!!!!
      

      【讨论】:

        猜你喜欢
        • 2014-08-12
        • 1970-01-01
        • 2017-02-25
        • 1970-01-01
        • 2017-04-25
        • 2013-09-13
        • 1970-01-01
        • 1970-01-01
        • 2011-11-27
        相关资源
        最近更新 更多