return 类型取决于 Scala 中的输入类型的通用函数?
Generic function where the return type depends on the input type in Scala?
我正在尝试编译此代码:
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))
}
当我尝试编译这段代码时出现错误:
type mismatch;
found : Square
required: S
case 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))
}
不过我好像漏掉了什么。这还是不行。
现在modifyShape
的body
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]
在此处查看详细信息:
Type mismatch on abstract type used in pattern matching
foo[S <: Shape]
意味着 foo
必须为 any S
工作,它是 Shape
的子类型。假设我取S := Shape with SomeTrait
,你不取returnIO[Shape with SomeTrait]
.
使用 F-bounded 类型参数尝试 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 提醒 link)
或具有 F-bounded 类型成员的 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-bound)
(详见@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 + 输入 class
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()
此处的解决方案是使用 GADT,一种广义代数数据类型。
在正常的 (non-generalized) ADT 中,case classes 将采用与密封特征完全相同的类型参数,并像本例中那样未经修改地传递它们:
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中,没有这样的限制。因此,Square
和 Cube
可以采用与 Shape
不同的类型参数集(在这种情况下是空集,根本就意味着 none),并且它们可以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
案例 class 作为类型参数传递给 Shape
.
的内容
但是Square
和Cube
没有必要将自己的类型作为类型参数传递给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))
我正在尝试编译此代码:
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))
}
当我尝试编译这段代码时出现错误:
type mismatch;
found : Square
required: S
case 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))
}
不过我好像漏掉了什么。这还是不行。
现在modifyShape
的body
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]
在此处查看详细信息:
Type mismatch on abstract type used in pattern matching
foo[S <: Shape]
意味着 foo
必须为 any S
工作,它是 Shape
的子类型。假设我取S := Shape with SomeTrait
,你不取returnIO[Shape with SomeTrait]
.
使用 F-bounded 类型参数尝试 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 提醒 link)
或具有 F-bounded 类型成员的 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-bound)
(详见@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 + 输入 class
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()
此处的解决方案是使用 GADT,一种广义代数数据类型。
在正常的 (non-generalized) ADT 中,case classes 将采用与密封特征完全相同的类型参数,并像本例中那样未经修改地传递它们:
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中,没有这样的限制。因此,Square
和 Cube
可以采用与 Shape
不同的类型参数集(在这种情况下是空集,根本就意味着 none),并且它们可以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
案例 class 作为类型参数传递给 Shape
.
但是Square
和Cube
没有必要将自己的类型作为类型参数传递给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))