返回类型取决于Scala中输入类型的泛型函数?

时间:2020-10-12 12:44:50

标签: scala pattern-matching

我正在尝试将此代码进行编译:

import cats.effect.IO

sealed trait Shape {
  val x: Int
}

case class Square(x: Int, y: Int) extends Shape
case class Cube(x: Int, y: Int, z: Int) extends Shape

def modifyShape[S <: Shape](shape: S): IO[S] = shape match {
  case s: Square => IO(s.copy(y = 5))
  case c: Cube => IO(c.copy(z = 5))
}

当我尝试编译此代码时,出现错误:

类型不匹配;
找到:正方形
必填:S
情况s:Square => IO(s.copy(y = 5))

如何使此代码起作用?

更新:
阅读评论和文章后,我尝试使用F-bound:

sealed trait Shape[A <: Shape[A]] { this: A =>
  val x: Int
}

case class Square(x: Int, y: Int) extends Shape[Square]
case class Cube(x: Int, y: Int, z: Int) extends Shape[Cube]

def modifyShape[S <: Shape[S]](shape: S): IO[S] = shape match {
  case s: Square => IO(s.copy(y = 5))
  case c: Cube => IO(c.copy(z = 5))
}

但是似乎我错过了一些东西。这仍然行不通。

2 个答案:

答案 0 :(得分:3)

现在modifyShape的身体

shape match {
  case s: Square => IO(s.copy(y = 5))
  case c: Cube => IO(c.copy(z = 5))
}

不满足其签名

def modifyShape[S <: Shape](shape: S): IO[S] 

在此处查看详细信息:

Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter?

Type mismatch on abstract type used in pattern matching

foo[S <: Shape]表示foo必须为S子类型的任何 Shape工作。假设我拿了S := Shape with SomeTrait,您没有返回IO[Shape with SomeTrait]

尝试使用带有F边界类型参数的GADT

sealed trait Shape[S <: Shape[S]] { this: S =>
  val x: Int
  def modifyShape: IO[S]
}

case class Square(x: Int, y: Int) extends Shape[Square] {
  override def modifyShape: IO[Square] = IO(this.copy(y = 5))
}
case class Cube(x: Int, y: Int, z: Int) extends Shape[Cube] {
  override def modifyShape: IO[Cube] = IO(this.copy(z = 5))
}

def modifyShape[S <: Shape[S]](shape: S): IO[S] = shape.modifyShape

https://tpolecat.github.io/2015/04/29/f-bounds.html @LuisMiguelMejíaSuárez提醒了链接)

或具有F界类型成员的GADT

sealed trait Shape { self =>
  val x: Int
  type S >: self.type <: Shape { type S = self.S }
  def modifyShape: IO[S]
}

case class Square(x: Int, y: Int) extends Shape {
  override type S = Square
  override def modifyShape: IO[Square] = IO(this.copy(y = 5))
}
case class Cube(x: Int, y: Int, z: Int) extends Shape {
  override type S = Cube
  override def modifyShape: IO[Cube] = IO(this.copy(z = 5))
}

def modifyShape[_S <: Shape { type S = _S}](shape: _S): IO[_S] = shape.modifyShape
// or  
// def modifyShape(shape: Shape): IO[shape.S] = shape.modifyShape

或GADT(无F限制)

(请参见 @MatthiasBerndt 的详细信息,answer和我对此的评论,此代码部分来自他的回答)

sealed trait Shape[A] {
  val x: Int
}

case class Square(x: Int, y: Int) extends Shape[Square]
case class Cube(x: Int, y: Int, z: Int) extends Shape[Cube]

def modifyShape[S](shape: Shape[S]): IO[S] = shape match {
  case s: Square => IO(s.copy(y = 5))
  case c: Cube   => IO(c.copy(z = 5))
}

或ADT +反射

sealed trait Shape {
  val x: Int
}

case class Square(x: Int, y: Int) extends Shape
case class Cube(x: Int, y: Int, z: Int) extends Shape

import scala.reflect.runtime.universe._

def modifyShape[S <: Shape : TypeTag](shape: S): IO[S] = (shape match {
  case s: Square if typeOf[S] <:< typeOf[Square] => IO(s.copy(y = 5))
  case c: Cube   if typeOf[S] <:< typeOf[Cube]   => IO(c.copy(z = 5))
}).asInstanceOf[IO[S]]

或ADT +类型类

sealed trait Shape {
  val x: Int
}

case class Square(x: Int, y: Int) extends Shape
case class Cube(x: Int, y: Int, z: Int) extends Shape

trait ModifyShape[S <: Shape] {
  def modifyShape(s: S): IO[S]
}
object ModifyShape {
  implicit val squareModifyShape: ModifyShape[Square] = s => IO(s.copy(y = 5))
  implicit val cubeModifyShape:   ModifyShape[Cube]   = c => IO(c.copy(z = 5))
}

def modifyShape[S <: Shape](shape: S)(implicit ms: ModifyShape[S]): IO[S] =
  ms.modifyShape(shape)

或ADT +磁铁

sealed trait Shape {
  val x: Int
}

case class Square(x: Int, y: Int) extends Shape
case class Cube(x: Int, y: Int, z: Int) extends Shape

import scala.language.implicitConversions

trait ModifyShape {
  type Out
  def modifyShape(): Out
}
object ModifyShape {
  implicit def fromSquare(s: Square): ModifyShape { type Out = IO[Square] } = new ModifyShape {
    override type Out = IO[Square]
    override def modifyShape(): IO[Square] = IO(s.copy(y = 5))
  }
  implicit def fromCube(c: Cube): ModifyShape { type Out = IO[Cube] } = new ModifyShape {
    override type Out = IO[Cube]
    override def modifyShape(): IO[Cube] = IO(c.copy(z = 5))
  }
}

def modifyShape(shape: ModifyShape): shape.Out = shape.modifyShape()

答案 1 :(得分:2)

这里的解决方案是使用GADT(广义代数数据类型)。

在普通(非通用)ADT中,案例类将采用与密封特征完全相同的类型参数,并将其传递给未修改的对象,如本例所示:

sealed trait Either[A, B]
case class Left[A, B](a: A) extends Either[A, B]
case class Right[A, B](b: B) extends Either[A, B]
// both Left and Right take two type parameters, A and B,
// and simply pass them through to sealed trait Either. 

在广义ADT中,没有这样的限制。因此,SquareCube被允许采用与Shape不同的类型参数集(在这种情况下为空集,根本没有意义),并且它们可以填充类型Shape的参数,而不是其自身的类型参数。在这种情况下,因为它们没有可以传递给Shape的任何类型参数,所以它们只是传递自己的类型。

sealed trait Shape[A] {
  val x: Int
}

case class Square(x: Int, y: Int) extends Shape[Square]
case class Cube(x: Int, y: Int, z: Int) extends Shape[Cube]

使用此声明,将编译以下定义:

  def modifyShape[S](shape: Shape[S]): IO[S] = shape match {
    case s: Square => IO(s.copy(y = 5))
    case c: Cube => IO(c.copy(z = 5))
  }

当Scala编译器发现shape实际上是Square时,很聪明地知道S必须是Square,因为这就是Square案例类作为类型参数传递给Shape

但是,SquareCube不必将自己的类型作为类型参数传递给Shape。例如,他们可以像本例一样通过另一个:

  sealed trait Shape[A] {
    val x: Int
  }

  case class Square(x: Int, y: Int) extends Shape[Cube]
  case class Cube(x: Int, y: Int, z: Int) extends Shape[Square]

  def changeDimension[S](shape: Shape[S]): IO[S] = shape match {
    case s: Square => IO(Cube(s.x, s.y, 42))
    case c: Cube => IO(Square(c.x, c.y))
  }


  val x: IO[Square] = changeDimension(Cube(3, 6, 25))