【问题标题】:Scala generic with trait matching具有特征匹配的 Scala 泛型
【发布时间】:2018-10-22 19:14:07
【问题描述】:

看看这段代码。

trait SomeMix {

}

trait Processor[T] {

  def processMix(t: T with SomeMix) = {
    println("processing T with Mix")
  }

  def processAsUsual(t:T)= {
    println("processing T")
  }

  def process(t:T) = {
    t match {
      case mix: SomeMix => processMix(mix) // <---- error here 
      case _ => processAsUsual(t)
    }
  }
}

愚蠢的 Scala 编译器在这里显示错误:

错误:(22, 39) 类型不匹配; 找到:mix.type(具有基础类型 SomeMix) 必需:带有 SomeMix 的 T 案例组合:SomeMix => processMix(mix)

它不明白我匹配到 SomeMix 的表达式已经是 T 类型。好的,让我们帮助他。更改代码:

   def process(t:T) = {
    t match {
      case mix: T with SomeMix => processMix(mix) // <---- warning here 
      case _ => processAsUsual(t)
    }
  }

现在它同意一切都是正确的,但显示警告:

警告:(22, 17) 抽象类型模式 T 未选中,因为它已被擦除消除 case mix: T with SomeMix => processMix(mix)

这里有什么避免错误和警告的好方法吗?

【问题讨论】:

    标签: scala generics traits


    【解决方案1】:

    Scala 编译器并不愚蠢。由于类型擦除,您无法检查 t 是 T with SomeMix 的实例。尝试使用带有静态分派的类型类,而不是动态类型分派。

    例如

    trait SomeMix {
      def someMethod: String = "test2"
    }
    
    class SomeType
    
    def process[T](t: T)(implicit P: Process[T]): Unit = P.process(t)
    
    trait Process[T] {
      def process(t: T): Unit
    }
    
    implicit val processString: Process[SomeType] = s =>
      println(s"processing $s as usual")
    implicit val processStringWithSomeMix: Process[SomeType with SomeMix] = s =>
      println(s"processing $s with mix ${s.someMethod}")
    
    process(new SomeType)
    process(new SomeType with SomeMix)
    

    【讨论】:

    • 谢谢你的例子。它给我带来了一些关于我的案子的新想法。但作为这个特定问题的解决方案,它并不好。处理逻辑从 trait Process 移到外部。这是不可接受的。但也许我可以稍微修改一下以适合我的情况
    • @Volchik 您可以将隐式 processStringprocessStringWithSomeMix 放入 object Process。在其伴生对象中为类型类定义隐式是 Scala 中的标准模式,它不是“将逻辑移到外面”。
    【解决方案2】:

    因为,正如你提到的,它绝对是T 的一个实例,你可以直接取消未检查的警告:

    case mix: (T @unchecked) with SomeMix
    

    请注意,它仍然未选中,并且在运行时仅测试匹配对象是 SomeMix 的实例;如果您更改为例如

    def process(t: Any) = ...
    

    你会得到不好的结果。

    【讨论】:

    • 谢谢。就我而言,它真的足够了。而且我知道与通用匹配是个坏主意。但就我而言,这只是在与 SomeMix 匹配的过程中消除类型 T 的结果。
    【解决方案3】:

    像这样?

    trait SomeMix {
    
    }
    
    trait Processor[T] {
    
      def processMix(t: SomeMix) = {
        println("processing SomeMix")
      }
    
      def processAsUsual(t:T)= {
        println("processing T")
      }
    
      def process(t:T) = {
        t match {
          case mix: SomeMix => processMix(mix)
          case _ => processAsUsual(t)
        }
      }
    }
    

    【讨论】:

    • 否,因为 processMix 只能与使用 SomeMix 扩展的 T 一起使用。在实际示例中,它调用了一些需要 T 和 SomeMix 功能的方法
    【解决方案4】:

    您可以像@ppressives 建议的那样在编译时执行此操作。 如果您真的想在运行时执行此操作,您应该找到一种在编译时间后将类型保留在那里的方法。在 Scala 中,执行此操作的标准方法是 TypeTags

    试试

    import reflect.runtime.universe.{TypeTag, typeOf}
    
    def typ[A: TypeTag](a: A) = typeOf[A]
    
    def process(t: T)(implicit ev: TypeTag[T with SomeMix], ev1: TypeTag[T]) = {
      t match {
        case mix if typ(t) <:< typeOf[T with SomeMix] => processMix(mix.asInstanceOf[T with SomeMix])
        case _ => processAsUsual(t)
      }
    }
    
    val p = new Processor[Int] {}
    p.process(10) //processing T
    
    val p1 = new Processor[Int with SomeMix] {}
    val ten = 10.asInstanceOf[Int with SomeMix]
    p1.process(ten) //processing T with Mix
    

    检查

    Pattern match of scala list with generics

    Pattern matching on generic type in Scala

    【讨论】:

    • 谢谢。您的示例效果很好,可以在我确实需要匹配泛型类型时使用。但我的情况更简单。我只需要匹配 SomeMix 就不会失去 T 类型的已知事实。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多