4

Given the following scala code:

sealed trait Color
case object Red extends Color
case object Blue extends Color

sealed trait Car {
  def isBroken: Boolean
  def color: Color
}

How can I define such a method:

def fixBrokenRedCar(c: A): B

That is, what should A and B be? The method should only accept a Car that is both Red and isBroken = true. Otherwise it should emit a compile error. Moreover, the output B should contain information on its type such that if I create another method destroyRedCar(c: B) and apply it on the output, it should compile accordingly.

jvliwanag
  • 1,508
  • 1
  • 13
  • 29

2 Answers2

5

You should move your data to the type-level then:

trait Bool
trait T extends Bool
trait F extends Bool

trait Color
trait Red extends Color
trait Blue extends Color

trait Car[Clr <: Color, Brkn <: Bool]

def fixBrokenCar[Cr <: Car[Red, T]](c: Cr) = new Car[Red, F]{}

scala> fixBrokenCar(new Car[Blue, T]{})
<console>:16: error: inferred type arguments [Car[Blue,T]] do not conform to method fixBrokenCar's type parameter bounds [Cr <: Car[Red,T]]
              fixBrokenCar(new Car[Blue, T]{})
              ^
<console>:16: error: type mismatch;
 found   : Car[Blue,T]
 required: Cr
              fixBrokenCar(new Car[Blue, T]{})
                           ^

scala> fixBrokenCar(new Car[Red, T]{})
res3: Car[Red,F] = $anon$1@67d9a642

To "destroy" it:

def destroyRedCar(c: Car[Red, _]) = true

scala> destroyRedCar(fixBrokenCar(new Car[Red, T]{}))
res10: Boolean = true

scala> destroyRedCar(new Car[Red, T]{})
res11: Boolean = true

scala> destroyRedCar(new Car[Blue, T]{})
<console>:15: error: type mismatch;
 found   : Car[Blue,T]
 required: Car[Red, ?]
              destroyRedCar(new Car[Blue, T]{})
                            ^

If you need to "mutate" Cr type (construct one type from another, more precisely):

trait Car[Clr <: Color, Brkn <: Bool] { 
   type Copy[C <: Color, B <: Bool] <: Car[C,B] // define "copy" type-method
}

trait BrandedCar[Clr <: Color, Brkn <: Bool] extends Car[Clr, Brkn] {
   type Copy[C <: Color, B <: Bool] = BrandedCar[C, B] // implement "copy" type-method
   def brand: String = "default"
}

def fixBrokenCar[Cr <: Car[Red, T]](c: Cr) = c.asInstanceOf[Cr#Copy[Red, F]]

def checkBrandedCar(c: BrandedCar[_, F]) = true // accepts only branded and fixed


scala> checkBrandedCar(new BrandedCar[Red, F]{})
res10: Boolean = true

scala> checkBrandedCar(new Car[Red, F]{})
<console>:15: error: type mismatch;
 found   : Car[Red,F]
 required: BrandedCar[?, F]
              checkBrandedCar(new Car[Red, F]{})
                              ^

scala> checkBrandedCar(fixBrokenCar(new BrandedCar[Red, T]{}))
res12: Boolean = true

You may also define some def copy[C <: Color, B <: Bool]: Copy[C, B] method inside Car's trait (like in case classes) instead of just asInstanceOf.

dk14
  • 22,206
  • 4
  • 51
  • 88
  • If i create `fixBrokenCar(new Car[Red, T] { def brand = "honda" })`, I lose `brand`. Any way around it? – jvliwanag Apr 07 '15 at 07:39
  • simple, just add `trait Car[Clr <: Color, Brkn <: Bool]{def brand: String}` to your trait definition – dk14 Apr 07 '15 at 08:10
  • btw, you don't loose it inside `fixBrokenCar` as scala generates structural type for you: `Car[Red,F]{def brand: String}` - you loose it `destroyRedCar` as it ignores the `brand` property - either way, explicit definition inside trait will help – dk14 Apr 07 '15 at 08:15
  • 1
    Say I create `trait BrandedCar[Clr <: Color, Brkn <: Bool] extends Car[Clr, Brkn] { def brand: String }`. Anything I can do with `fixBrokenCar` such that if I pass on a `BrandedCar`, it still returns `BrandedCar`? Or am I thinking about this the wrong way? – jvliwanag Apr 07 '15 at 09:35
2

I am assuming that you can change your class structure.

One way to achieve what you want is using Type Level Programming. There is a pretty good stack overflow post on this here: Scala type programming resources

Here is some sample code based on your original code which demonstrates how this can be achieved using the type system within Scala.

/* 
 * Color traits I've left the same except converted the objects 
 * to classes. This would work just as well with traits. It might 
 * even be better if the Colors such as Red and Blue are traits
 * themselves that extend Color, I'm still just learning this 
 * method myself.
 */
sealed trait Color
class Red extends Color
class Blue extends Color

/* New trait to represent whether something is broken or not */
sealed trait IsBroken    
class Broken extends IsBroken
class NotBroken extends IsBroken

/* Change Car trait to have two type parameters, broken and color */   
trait Car[T <: Color, S <: IsBroken]

/* fixBrokenCar signature includes whether a car is broken and it's color */
def fixBrokenRedCar(c: Car[Red, Broken]): Car[Red, NotBroken]
    = new Car[Red, NotBroken]{}

val brokenRedCar = new Car[Red, Broken]{}
val fixedRedCar = new Car[Red, NotBroken]{}
val brokenBlueCar = new Car[Blue, Broken]{}

/* Compiles */
fixBrokenRedCar(brokenRedCar)

/* Doesn't compile */
fixBrokenRedCar(fixedRedCar)

/* Doesn't compile */
fixBrokenRedCar(brokenBlueCar)
Community
  • 1
  • 1
Mike Curry
  • 1,609
  • 1
  • 9
  • 12