3

Consider the following code:

case class Vector3(var x: Float, var y: Float, var z: Float)
{
  def add(v: Vector3): Unit =
  {
    this.x += v.x
    this.y += v.y
    this.z += v.z
  }
}

As you can see, the case class holds mutable state. It is highly discouraged to do this and normally I'd agree and absolutely stick to this "rule", but here goes the whole story.

I'm using Scala to write a little 3d-game-engine from scratch. So first I thought about using a (much more) functional style, but then the garbage-collector would kick in too often.

Think about it for a moment: I have dozens and dozens of entities in a test-game. All of them have a position (Vector3), an orientation (Vector3), a scale (Vector3) and a whole lot of Matrices too. If I was about to go functional in these classes (Vector3 and Matrix4) and make them immutable I would return hundreds of new objects each frame, resulting in a huge fps-loss because, let's face it, GC has its uses, but in a game-engine and with OpenGL... not so much.

Vector3 was a class before, but it is a case class now, because somewhere in the code I need pattern-matching for it.

So, is it really that bad to use a case-class that holds mutable state?

Please do not turn this into a discussion about "Why do you even use Scala for a project such as that?" I know that there may be better alternatives out there, but I'm not interested in writing (yet another) engine in C++, nor am I too eager to dive into Rust (yet).

Peter Neyens
  • 9,770
  • 27
  • 33
  • 3
    I don't know if there is a good answer to that question. As you said it is highly discouraged to implement case classes with mutable state but in my opinion it's a convention which you should care about when you implement a library. In such a case, it can be quite nasty if you can mutate the state of case class where nobody expects it. But if you document it well and it is quite important to the necessary performance you can use the available features. – M. Reif Sep 24 '15 at 19:53
  • 2
    Just an outside opinion: When it comes to low-level programming, there are countless examples out there in the wild that step away from conventions and established ways of doing things. Believe it or not, C++ has conventions and good practices too, but these good practices and readability are often sacrificed for performance optimizations. – Ruslan Sep 24 '15 at 20:18
  • take a look at this question: http://stackoverflow.com/questions/4653424/what-are-the-disadvantages-to-declaring-scala-case-classes – anquegi Sep 24 '15 at 20:28

1 Answers1

4

I would say it is bad to use case classes with mutable state, but only because they override your equals and hashCode methods. Somewhere in your code you may check whether a == b and find out that they are equal. Later they may be different because they are mutable. In the very least, they are dangerous to use in combination with hash-based collections.

However, you don't seem to be in need of all functionality a case class provides. What you really seem to require is an extractor for pattern matching, so why not define it? Furthermore, the static factory apply, and a readable toString-representation may be convenient, so you could implement them.

How about:

class Vector (var x: Float, var y: Float, var z: Float) {
  override def toString = s"Vector($x, $y, $z)"
}

object Vector {
  def apply(x: Float, y: Float, z: Float) = new Vector(x, y, z)

  def unapply(v: Vector): Option[(Float, Float, Float)] = Some((v.x, v.y, v.z))
}
Kulu Limpa
  • 3,501
  • 1
  • 21
  • 31
  • It would be better just to use ready to use linear algebra collections ( http://stackoverflow.com/questions/12449427/does-scalala-provide-straightforward-way-of-inserting-vector-into-matrix ) instead of defining own vector – ayvango Sep 25 '15 at 00:38
  • Thank you, I already have a convinient toString-method like that. Apply is all I need to do pattern matching? I guess I will need an `equals` and `hashCode` too? –  Sep 25 '15 at 05:55
  • @ayvango: I want to learn something new. To learn something new I have to experiment. In that case I want to improve my Scala and learn some more computer graphics on the same run. I've written an engine in C++ already, but there I used GLM. I've also used libgdx and the like in the past. Now I want to dive deeper and I already implemented most of the maths and verified that it is - atomically - correct. –  Sep 25 '15 at 05:57
  • @Teolha it just a matter of time allocation. You may focus on reimplementing linear algebra, or may spend time for some advanced game engine features. There are a lot of different ways for casing shadows, for example. And you may try several approaches if you would have spare time. – ayvango Sep 25 '15 at 10:18
  • @ayvango Thank you, I've made my decision before I started programming. No sense to discuss this :) –  Sep 25 '15 at 10:55
  • 1
    @Teolha You don't need an `equals` and `hashCode` method for mutable types. If needed, you may implement a helper method to check whether two instances have currently the same value. For pattern matching you only need to implement `unapply`. – Kulu Limpa Sep 26 '15 at 08:39