Sasgorilla
Sasgorilla

Reputation: 3130

How can I compose Scala traits?

Say I want to define a ball's characteristics via traits:

trait Size {
  val size: String
}

trait Big extends Size {
  val size = "big"
}

trait Small extends Size {
  val size = "small"
}

trait Bouncy {
  def bounce: Unit = println("boing boing boing")
}

class Ball

val bigBouncyBall = new Ball with Big with Bouncy

So far, so good. But how can I mutate my ball's size while preserving its bounciness?

def shrink(ball: Ball) = 
  ball with Small // invalid syntax. Does not compile

def shrink(ball: Ball) =
  ball.asInstanceOf[Ball with Small] // Nope. Ball no longer bounces!

In other words, can I override a specific trait while preserving others?

Upvotes: 1

Views: 230

Answers (1)

Dima
Dima

Reputation: 40508

You are mixing types of objects with properties. Traits are intended to represent the former while instance members are for the latter. Can you "mutate" a pear into an apple? That doesn't make sense, right?

Something like this:

  case class Ball(isBig: Boolean, isBouncy: Boolean) {
    def shrink = copy(isBig = false)
  }

Seems like a more sensible implementation in your case. Note: this is still not mutating the instance per se (it is generally not a good idea in scala, and should be avoided), but rather returns a different instance with the modified property. You could make it mutate in place instead, but, like I said, it's not a good idea, so, I won't go there ...

Now, which features are attributes of a type, and which are properties of an instance is not really set in stone, that depends on the needs of the application that will use your data model.

For example, if the app deals with different shapes, but only cares about the number of vertices, it might just have

class Shape(val vertices: Int)
val circle = new Shape(0) 
val rect = new Shape(4)

etc., but if it gets more involved with the properties of specific shapes, it might need more specialized traits like Polygon or Regular, and classes like

trait Regular { def size: Float }
case class Circle(val size: Float) extends Shape(0) with Regular
case class Square(val size: Float) extends Shape(4) with Regular with Polygon

This comes with "pros" and "cons". On one hand, you can now create robust specialized functions, that only deal with particular kinds of shapes:

def perimeter(p: Polygon with Regular) = p.size * p.vertices 
def area(p: Polygon with Regular) = p.vertices * Math.pow(p.size, 2)/ (4 *  Math.tan(Math.PI/p.vertices))

But can no longer "mutate" a circle into a square - it simply makes no sense.

Upvotes: 2

Related Questions