如何在协变位置上使用协变类型参数来变通

时间:2018-12-04 20:38:34

标签: scala generics types covariance contravariance

作为管道/工作流/执行管理系统的一部分,我有一个内部DSL,用于描述可执行任务,并且该DSL具有允许通过管道连接任务的结构(DSL看起来像unix管道,其底层实现是实际上通过Unix管道)。

下面是一个经过编译的独立示例,

class Void
class Data
class Text extends Data
class Csv extends Text
class Tsv extends Text

object Piping {
  trait Pipe[-In,+Out] {
    def |[Out2](next: Pipe[Out,Out2]): Pipe[In,Out2] = Chain(this, next)
  }

  case class Chain[-A,+B](a: Pipe[A,_], b: Pipe[_,B]) extends Pipe[A,B]

  case class Cat() extends Pipe[Void,Text]
  case class MakeCsv() extends Pipe[Text,Csv]
  case class MakeTsv() extends Pipe[Text,Tsv]
  case class CsvToTsv() extends Pipe[Csv,Tsv]
  case class Column() extends Pipe[Text,Text]
}

import Piping._
Cat() | MakeCsv() | Column()

它使用类型参数来确保您无法在期望使用不同数据类型的事物之间进行传递,并且一切正常。

现在,我想添加一种方法,该方法允许传递给可选任务,该任务可以是Some[Pipe]None,这是我的问题所在:

我想将特征扩展为:

trait Pipe[-In,+Out] {
  def |[Out2](next: Pipe[Out,Out2]): Pipe[In,Out2] = Chain(this, next)

  def |(next: Option[Pipe[Out,Out]]): Pipe[In,Out] = next match {
    case Some(n) => this | n
    case None    => this
  }
}

或者用英语,我想输入一个Option[Pipe],其输入类型是Out的任何超类型,而输出类型是Out的任何子类型。但是,这使我感到恐惧:

error: covariant type Out occurs in contravariant position in type Option[Piping.Pipe[Out,Out]] of value next

我了解为什么会收到错误消息,但我不知道是否有任何方法可以表达我想要的类型关系,而不会导致该错误!

1 个答案:

答案 0 :(得分:4)

为什么“害怕”,对此有什么“恐惧” ...

标准解决方法:

class Void
class Data
class Text extends Data
class Csv extends Text
class Tsv extends Text

object Piping {
  trait Pipe[-In,+Out] {
    def |[Out2](next: Pipe[Out,Out2]): Pipe[In,Out2] = Chain(this, next)
    def |[T >: Out](next: Option[Pipe[T, T]]): Pipe[In, T] = next match {
      case Some(n) => this | n
      case None    => this
    }
  }

  case class Chain[-A,+B](a: Pipe[A,_], b: Pipe[_,B]) extends Pipe[A,B]

  case class Cat() extends Pipe[Void,Text]
  case class MakeCsv() extends Pipe[Text,Csv]
  case class MakeTsv() extends Pipe[Text,Tsv]
  case class CsvToTsv() extends Pipe[Csv,Tsv]
  case class Column() extends Pipe[Text,Text]
}

似乎可以正常工作:

import Piping._
Cat() | MakeCsv() | Column() | Some(Column()) | Option.empty[Pipe[Text, Text]] | Column()