代码之家  ›  专栏  ›  技术社区  ›  y2k-shubham

Scala类型检查的奇怪失败

  •  1
  • y2k-shubham  · 技术社区  · 7 年前

    我的应用程序要求 参数提供程序 trait 可以添加到任何 class 允许通过 任意数量的参数 任何 类型 用它。

    trait Arg
    case class NamedArg(key: String, value: Any) extends Arg
    
    // I extend my classes with this trait
    trait ArgsProvider {
      val args: Seq[Arg]
    
      lazy val namedArgs: Map[String, Any] = {
        args.filter(_.isInstanceOf[NamedArg]).
          map(_.asInstanceOf[NamedArg]).
          map(arg => arg.key -> arg.value).toMap
      }
    
      ...
    }
    

    然后我可以提取 NamedArg s来自 args 属于 ArgsProvider 使用其 key 如下所示

    trait ArgsProvider {
      ...
    
      /*
       * Method that takes in a [T: ClassTag] and a (key: String) argument
       * (i) if key exists in namedArgs: Map[String, Any]
       *     - Returns Some(value: T) if value can be casted into T type
       *     - Throws Exception if value can't be casted into T type
       * (ii) if key doesn't exist in namedArgs
       *    Returns None
       */
      def getOptionalTypedArg[T: ClassTag](key: String): Option[T] = {
        namedArgs.get(key).map { arg: Any =>
          try {
            arg.asInstanceOf[T]
          } catch {
            case _: Throwable => throw new Exception(key)
          }
        }
      }
      ...
    }
    

    尽管看起来 高度不直观 冗长的 ,此设计有效 完美无瑕 为了我。然而,在写某些 unit tests ,我最近发现 主要漏洞 其中: 它无法执行 type-checking . (或者至少我是这么推断的)


    更具体地说 不会引发任何异常 当我尝试 type-cast 提供的 arg 进入 类型错误 . 例如:

    // here (args: Seq[NamedArg]) overrides the (args: Seq[Arg]) member of ArgsProvider
    case class DummyArgsProvider(args: Seq[NamedArg]) extends ArgsProvider
    
    // instantiate a new DummyArgsProvider with a single NamedArg having a String payload (value)
    val dummyArgsProvider: DummyArgsProvider = DummyArgsProvider(Seq(
        NamedArg("key-string-arg", "value-string-arg")
    ))
    
    // try to read the String-valued argument as Long
    val optLong: Option[Long] = dummyArgsProvider.getOptionalTypedArg[Long]("key-string-arg")
    

    而人们会期望上面的代码 throw Exception ; 令我沮丧的是,它工作得非常好,并返回以下输出(on Scala REPL )

    optLong:Option[长]=一些(值字符串arg)


    我的问题是:

    • 为什么这里的类型检查失败?
    • 一般来说,Scala的类型检查在什么情况下会失败?
    • 这个设计可以改进吗?

    我正在使用

    • Scala 2.11.11
    • SBT 1.0.3
    2 回复  |  直到 7 年前
        1
  •  3
  •   Kolmar    7 年前

    正如@AlexeyRomanov所言, as/isInstanceOf[T] 不要使用 ClassTag .

    您可以改用模式匹配,它会检查 类别标签 ,如果有可用的:

    trait ArgsProvider {
      /* ... */
    
      def getOptionalTypedArg[T: ClassTag](key: String): Option[T] = {
        namedArgs.get(key).map {
          case arg: T => arg
          case _ => throw new Exception(key)
        }
      }
    }
    

    或者您可以使用 类别标签 直接:

    import scala.reflect.classTag
    
    def getOptionalTypedArg[T: ClassTag](key: String): Option[T] = {
      namedArgs.get(key).map { arg =>
        classTag[T].unapply(arg).getOrElse(throw new Exception(key))
      }
    }
    
        2
  •  1
  •   Juh_    7 年前

    您在类型擦除方面遇到问题: Option[Long] 实际上,存储字符串“value String arg”,而不关心被擦除的类型。

    然而,如果你这样做 optLong.get 然后,它将尝试将其转换为预期输出的Long。你会得到ClassCastException


    只是一点评论:

    代替

    val namedArgs: Map[String, Any] = {...}
    

    通过

    val namedArgs: Map[String, Any] = args.collect{
      case NameArg(k, v) => k -> v
    }(collection.breakout)
    

    此外,在getOptionalTypedArg中,不要抓住所有可丢弃的东西。这是一种糟糕的做法(您可能会发现内存错误和其他致命错误,而您不应该这样做)。在您的情况下,您希望捕获ClassCastException。在另一种情况下,如果您不确切知道哪种可丢弃,请尝试使用 NonFatal