如何解决在逆变位置使用协变类型参数的问题

How to workaround using a covariant type parameter in a contravariant position

作为 pipeline/workflow/execution 管理系统的一部分,我有一个用于描述可执行任务的内部 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()

它使用类型参数来确保您不能在需要不同数据类型的事物之间进行管道传输,并且一切正常。

现在我想添加一个允许管道连接到 optional 任务的方法,它可以是 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

我明白为什么我会收到错误,但我想不通的是是否有任何方法可以表达我想要的类型关系而不会导致该错误!

为什么 "dreaded",那是什么 "dreaded"...

标准解决方法:

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()