【问题标题】:Leveraging a generic return type in Scala在 Scala 中利用泛型返回类型
【发布时间】:2021-12-13 10:21:52
【问题描述】:

所以我想使用通用返回类型,并能够在函数中使用该类型的信息。不确定这是可能的,但这是我想要的:

  def getStuff[A](a: MyObj, b: String): Option[A] = {
    // do some stuff
    A match {
      case String => Some(a.getString(b))
      case Integer => Some(a.getInt(b))
      ...
      case _ => None
    }
  }

但是,如您所知,A match 是不可能的。关于如何实现这一目标的任何想法?

【问题讨论】:

    标签: scala generics polymorphism pattern-matching


    【解决方案1】:

    这是使用类型类的经典案例:

    trait StuffGetter[T] { // typeclass
      def get(obj: MyObj, s: String): Option[T]
    }  
    
    implicit val stringGetter = new StuffGetter[String] {
       def get(o: MyObj, s: String): Option[String] = ???
    }
    implicit val intGetter = new StuffGetter[Int] {
       def get(o: MyObj, s: String): Option[Int] = ???
    }
    
    def getStuff[A](a: MyObj, b: String)(implicit ev: StuffGetter[A]): Option[A] =
      ev.get(a, b)
    
    val stuff0 = getStuff[String](obj, "Hello")  // calls get on stringGetter
    val stuff1 = getStuff[Int](obj, "World") // call get on intGetter
    val stuff2 = getStuff[Boolean](obj, "!") // Compile-time error
    

    StuffGetter 特征定义了您要对泛型类型执行的操作,并且该特征的每个 implicit 值都提供了特定类型的实现。 (对于自定义类型,这些通常放在该类型的伴随对象中;编译器会在那里查找它们)

    当调用getStuff 时,编译器将查找具有匹配类型的StuffGetterimplicit 实例。如果不存在这样的实例,这将失败,否则将在ev 参数中传递。

    这样做的好处是“匹配”是在编译时完成的,并且在编译时也会检测到不受支持的类型。

    【讨论】:

    • 那里只有两个 cets。在 scala 3 中,也可以使用 inline + erasedValue (如此处:scastie.scala-lang.org/ICi94Cy8QVOkZaOYBav4Lw)。无论如何,我认为隐式解决方案总是更好。
    • 感谢您的提示。恐怕我不会说 Scala 3,但这对其他人有用!
    【解决方案2】:

    从概念上讲,我们可以区分运行时的模式匹配,看起来像这样

    def getStuff[A](...) =
      A match {
        ...
      }
    

    编译时的模式匹配看起来像这样

    def getStuff[A](...)(implicit ev: Foo[A]) = {
       ev.bar(...)
    }
    

    要理解的关键概念是,类型在运行时不存在,因为它们在编译后被“擦除”,因此一旦程序运行,就没有足够的信息来对类型进行模式匹配。然而在编译时,也就是在程序运行之前,类型确实存在,Scala 提供了一种方法来要求编译器通过隐式/给定机制对它们进行有效的模式匹配,看起来像这样

    // type class requirements for type A 
    trait StringConverter[A] {
      def getOptValue(b: String): Option[A]
    }
    
    // evidence which types satisfy the type class 
    implicit val intStringConverter: StringConverter[Int] = (b: String) => b.toIntOption
    implicit val strStringConverter: StringConverter[String] = (b: String) => Some(b)
    implicit def anyStringConverter[A]: StringConverter[A] = (b: String) => None
    
    // compile-time pattern matching on type A
     def getStuff[A](b: String)(implicit ev: StringConverter[A]): Option[A] = {
       ev.getOptValue(b)
      }
    
    getStuff[Int]("3")     // : Option[Int] = Some(value = 3)
    getStuff[String]("3")  // : Option[String] = Some(value = "3")
    getStuff[Double]("3")  // : Option[Double] = None
    

    这种编译时模式匹配称为type class模式。

    理解类型和类之间的区别是 Scala https://docs.scala-lang.org/tutorials/FAQ/index.html#whats-the-difference-between-types-and-classes 中的基本概念之一,深入了解它有助于理解如何编写类型类。

    【讨论】:

      【解决方案3】:

      使用类似于Getter的自定义类型类:

      trait KeyedGetter[S, K, A]:
        def get(s: S, key: K): Option[A]
      
      case class MyObj(ints: Map[String, Int], strs: Map[String, String])
      
      object MyObj:
        given KeyedGetter[MyObj, String, Int] with
          def get(m: MyObj, k: String) = m.ints.get(k)
      
        given KeyedGetter[MyObj, String, String] with
          def get(m: MyObj, k: String) = m.strs.get(k)
      
      def getStuff[A](m: MyObj, key: String)(using g: KeyedGetter[MyObj, String, A]): Option[A] =
        g.get(m, key)
      
      

      使用类标签:

      case class MyObj(ints: Map[String, Int], strs: Map[String, String])
      
      import reflect._
      def getStuff[A](m: MyObj, key: String)(using ct: ClassTag[A]): Option[A] = (ct match
        case _ if ct == classTag[String] => m.strs.get(key)
        case _ if ct == classTag[Int] => m.ints.get(key)
        case _ => None
      ).asInstanceOf[Option[A]]
      

      如果擦除的类型不足,对于类型标签的类似方法,请参阅this answer(并忽略其余部分)。

      【讨论】:

        猜你喜欢
        • 2019-04-10
        • 2016-08-15
        • 2018-02-23
        • 1970-01-01
        • 1970-01-01
        • 2016-12-24
        • 2014-08-25
        • 2022-10-14
        • 1970-01-01
        相关资源
        最近更新 更多