跨不同父实例统一内部类

时间:2018-09-26 08:40:38

标签: scala type-projection

编辑:添加了有关F[_]

的信息

这是设置。我们有一个包含一个类型的父类,以及一个在所有父实例上处理该类型的类型投影的方法:

class Parent[F[_]] {

  // These depend on information from Parent (ie F) so
  // cannot be moved outside
  trait Inner { def execute[A]: F[A] }
  case class Foo(i: Int) extends Inner { ... }
  case class Bar(s: String) extends Inner { ... }

  def process(value: Parent#Inner): Unit = value match {
    case Foo(_) => println("integer")
    case Bar(_) => println("string")
  }

}

问题在于,在大小写匹配中,对FooBar的引用是this.Inner,而不是Parent#Inner。因此,以下操作将失败:

val foo = (new Parent[IO]).Foo(5)
val processer = new Parent[IO]
processer.process(foo)  // match error

一种解决方法是将def process更改为:

  def process(value: Parent#Inner): Unit = value.asInstanceOf[this.Inner] match {
    case Foo(_) => println("integer")
    case Bar(_) => println("string")
  }

(请注意新的.asInstanceOf)。

但是,这并不令人满意。

除了将def process提取到某个地方的第三方类之外,还有没有更好的方法来实现我们期望的行为?

编辑:

不幸的是,由于对Parent的依赖,需要在F[_]内部定义这些类。从理论上讲,我们可以按照初始答案的建议将它们移出外部,但这会在其他地方引入过多的工作和多样性,因为我们需要通过Inner

参数化每个F[_]子类

编辑2:

一种可能的解决方案是像这样重新编写process

def process(value: Parent#Inner): Unit = value match {
  case _: Parent[F]#Foo => println("integer")
  case _: Parent[F]#Bar => println("string")
}

但这意味着我们不能使用Foo的未应用方法。以下是无效的:

case Parent[F]#Foo(_) => println("integer")

如果使用Foo例如Foo[A, B, C](a: A, b: B, c: C),则这意味着match语句变为:

case _: Parent[F]#Foo[A, B, C] @unchecked => ...

这会在模式匹配中引入更多的复杂性和失败的可能性。

2 个答案:

答案 0 :(得分:2)

您应该使用依赖于路径的类型编写

class Parent[F[_]] {
  trait Inner {
    def execute[A]: F[A]
  }

  case class Foo(i: Int) extends Inner {
    override def execute[A]: F[A] = ???
  }

  case class Bar(s: String) extends Inner {
    override def execute[A]: F[A] = ???
  }

  def process(value: Inner): Unit = value match {
    case Foo(_) => println("integer")
    case Bar(_) => println("string")
  }
}

val processer = new Parent[IO]
val foo: processer.Inner = processer.Foo(5)
processer.process(foo)

或带有类型投影

class Parent[F[_]] {
  trait Inner {
    def execute[A]: F[A]
  }

  case class Foo(i: Int) extends Inner {
    override def execute[A]: F[A] = ???
  }

  case class Bar(s: String) extends Inner {
    override def execute[A]: F[A] = ???
  }

  def process(value: Parent[F]#Inner): Unit = value match {
    case _: Parent[F]#Foo => println("integer")
    case _: Parent[F]#Bar => println("string")
  }
}

val foo: Parent[IO]#Inner = new Parent[IO].Foo(5)
val processer = new Parent[IO]
processer.process(foo) 

参数化Foo且未检查类型匹配的示例:

class Parent[F[_]] {
  trait Inner {
    def execute[A]: F[A]
  }

  case class Foo[B](i: Int) extends Inner {
    override def execute[A]: F[A] = ???
  }

  case class Bar(s: String) extends Inner {
    override def execute[A]: F[A] = ???
  }

  def process(value: Parent[F]#Inner): Unit = value match {
    case _: Parent[F]#Foo[_] => println("integer")
    case _: Parent[F]#Bar => println("string")
  }
}

答案 1 :(得分:1)

您可以在模式匹配中使用类型投影:

def process(value: Parent[F]#Inner): Unit = value match {
  case _: Parent[F]#Foo => println("integer")
  case _: Parent[F]#Bar => println("string")
}

另一种允许您使用unapply的方法:

// Start writing your ScalaFiddle code here
class Parent[F[_]] { self =>

  // These depend on information from Parent (ie F) so
  // cannot be moved outside
  trait Inner { def parent = self }
  case class Foo(i: Int) extends Inner
  case class Bar(s: String) extends Inner

  def process(value: Parent[F]#Inner): Unit = {
    val parent = value.parent
    value match {
      case parent.Foo(_) => println("integer")
      case parent.Bar(_) => println("string")
    }
  }

}

val foo = (new Parent[List]).Foo(5)
val processer = new Parent[List]
processer.process(foo) // integer

如果将value.parent.Foo(_)设为Inner#parent,也可以使用val作为模式。