简体   繁体   中英

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

As part of a pipeline/workflow/execution management system I have an internal DSL for describing executable tasks, and that DSL has constructs to allow the connection of tasks via pipes (the DSL looks like unix pipes, and the implementation underneath is actually via unix pipes).

The following is a standalone, grossly simplified, example that compiles:

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

It uses type parameters to ensure that you can't pipe between things that expect different datatypes, and it all works.

Now I want to add a method that allows piping to an optional task, which can be either Some[Pipe] or None , and here's where my problem starts:

I want to extend the trait to be:

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
  }
}

Or in English, I want to take in an Option[Pipe] whose input type is any supertype of Out and whose output type is any subtype of Out . However this gives me the dreaded:

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

I understand why I'm getting the error, what I can't figure out is if there's any way to express the type relationships I want that won't cause that error!

Why "dreaded", what's "dreaded" about that...

Standard workaround:

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]
}

Seems to work just fine:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM