Now modifyShape
's body
shape match {
case s: Square => IO(s.copy(y = 5))
case c: Cube => IO(c.copy(z = 5))
}
just doesn't satisfy its signature
def modifyShape[S <: Shape](shape: S): IO[S]
See details here:
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]
means that foo
has to work for any S
that is a subtype of Shape
. Suppose I take S := Shape with SomeTrait
, you don't return IO[Shape with SomeTrait]
.
Try GADT with F-bounded type parameter
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 reminded the link)
or GADT with F-bounded type member
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
or GADT (without F-bound)
(see details in @MatthiasBerndt's answer and my comments to it, this code portion is from his 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))
}
or ADT + reflection
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]]
or ADT + type 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)
or ADT + magnet
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()
def modifyShape(shape: Shape): IO[Shape]
not enough? – Futrell