【问题标题】:Generic strongly-typed scala method to retrieve item of a particular type from a collection用于从集合中检索特定类型的项目的通用强类型 scala 方法
【发布时间】:2015-12-04 13:25:46
【问题描述】:

想象一下我有这门课:

class Example {
    val list = List(new Apple(), new Orange(), Banana());
    def getIfPresent[T <: Fruit] : Option[T] = list.collectFirst { case x : T => x }
}

你可以这样使用它:

val example = new Example();
match example.getIfPresent[Apple] {
    case Some(apple) => apple.someAppleSpecificMethod();
    case None => println("No apple");
}

现在,当然,这在 JVM 中不起作用,因为类型擦除。 getIfPresent 仅匹配 collectFirst 部分函数中的类型 Fruit,而不是调用中指定的实际类型。

我试图了解类型标签和类标签,但我真的不知道我将如何实现上述方法。我看到的例子试图做非常不同的事情。我如何才能使用 TypeTags 或其他我不知道的机制来实现我想要的方法?


编辑:下面 m-z 的答案是完整的解决方案,但这是我的示例代码的外观:

class Example {
    val list = List(new Apple(), new Orange(), Banana());
    def getIfPresent[T <: Fruit : ClassTag] : Option[T] = list.collectFirst { case x : T => x }
}

只需添加: ClassTag

【问题讨论】:

    标签: scala generics reflection


    【解决方案1】:

    您可以在一定程度上使用ClassTag 来做到这一点。

    import scala.reflect.ClassTag
    
    // Modify to apply whatever type bounds you find necessary
    // Requires Scala ~2.11.5 or greater (not sure of the exact version, but 2.11.1 does not work, and 2.11.5 does)
    def findFirst[A : ClassTag](list: List[Any]): Option[A] =
        list collectFirst { case a: A => a }
    
    val l = List(1, "a", false, List(1, 2, 3), List("a", "b"))
    
    scala> findFirst[Boolean](l)
    res22: Option[Boolean] = Some(false)
    
    scala> findFirst[Long](l)
    res23: Option[Long] = None
    

    但是ClassTag 有一些注意事项,因为它只会匹配类,而不是类型:

    scala> findFirst[List[String]](l)
    res24: Option[List[String]] = Some(List(1, 2, 3)) // No!
    

    您可以使用TypeTag 来解决这个问题,但它不适用于List[Any]。这是一种可能的(有点丑陋的)技巧:

    import scala.reflect.runtime.universe.{typeOf, TypeTag}
    
    case class Tagged[A : TypeTag](a: A) {
        def tpe = typeOf[A]
    }
    
    implicit class AnyTagged[A : TypeTag](a: A) {
        def tag = Tagged(a)
    }
    
    def findFirst[A : TypeTag](list: List[Tagged[_]]): Option[A] =
        list collectFirst { case tag @ Tagged(a) if(tag.tpe =:= typeOf[A]) => a.asInstanceOf[A] }
    

    我能想到的保持每个元素的TypeTag 的唯一方法是使用包装类来保持它。所以我必须像这样构建列表:

    val l = List(1.tag, "a".tag, false.tag, List(1, 2, 3).tag, List("a", "b").tag)
    

    但它有效:

    scala> findFirst[List[String]](l)
    res26: Option[List[String]] = Some(List(a, b))
    

    使用TypeTags 可能有一种更优雅的方式来构建这样的列表。


    为了好玩,您还可以尝试使用HListselectshapeless 执行此操作。不同之处在于select 不会返回Option[A],而是返回A(你想要的类型),但是如果HList 不包含A它不会编译 .

    import shapeless._
    
    val l = 1 :: "a" :: false :: List(1, 2, 3) :: List("a", "b") :: HNil
    
    scala> l.select[Boolean]
    res0: Boolean = false
    
    scala> l.select[Boolean]
    res1: Boolean = false
    
    scala> l.select[List[String]]
    res2: List[String] = List(a, b)
    
    scala> l.select[Long]
    <console>:12: error: Implicit not found: shapeless.Ops.Selector[shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.::[List[Int],shapeless.::[List[String],shapeless.HNil]]]]], Long]. You requested an element of type Long, but there is none in the HList shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.::[List[Int],shapeless.::[List[String],shapeless.HNil]]]]].
                  l.select[Long]
                          ^
    

    【讨论】:

      猜你喜欢
      • 2015-11-30
      • 1970-01-01
      • 2015-02-08
      • 2013-03-24
      • 1970-01-01
      • 2011-02-01
      • 1970-01-01
      • 2019-04-24
      • 2021-03-17
      相关资源
      最近更新 更多