Let's say, in Kotlin we have some simple 2D-vector class.
It has two methods, one (normalize
) which mutates the object,
and one (length
) which does not:
import kotlin.math.sqrt
class Vector(private var x: Double, private var y: Double) {
fun length() = sqrt(x * x + y * y)
fun normalize() {
val l = length()
x /= l
y /= l
}
}
Imagine Vector
being some kind of domain entity (with an ID, etc.),
such that normalize
needs to mutate it
instead of just returning a new, immutable normalized Vector
(value object).
We could now use it like that:
fun foo(v: Vector) {
v.normalize()
}
fun bar(v: Vector) {
println(v.length())
}
fun main() {
val myVector = Vector(3.0, 4.0)
foo(myVector)
bar(myVector)
}
However, we might want to make sure bar
can not mutate our object.
It should only be allowed to call the non-mutating methods.
To achieve this, we need to provide two different classes:
open class Vector(protected var x: Double, protected var y: Double) {
fun length() = sqrt(x * x + y * y)
}
class MutableVector(x: Double, y: Double) : Vector(x, y) {
fun normalize() {
val l = super.length()
x /= l
y /= l
}
}
Now we can adjust the rest of our code, such that the desired property of bar
is satisfied:
fun foo(v: MutableVector) {
v.normalize()
}
fun bar(v: Vector) {
println(v.length())
}
fun main() {
val myVector = MutableVector(3.0, 4.0)
foo(myVector)
bar(myVector)
}
MutableList
and List
in Kotlin's standard library follow a similar approach.
Actually, Kotlin is in good company here.
Other languages (not just Java) utilize similar mechanisms.
So, let's translate this straight into C++:
#include <cmath>
#include <iostream>
class vector {
public:
vector(double x, double y): x_(x), y_(y) {}
double length() {
return std::sqrt(x_ * x_ + y_ * y_);
}
protected:
double x_;
double y_;
};
class mutable_vector: public vector {
public:
mutable_vector(double x, double y): vector(x, y) {}
void normalize() {
auto l = length();
x_ /= l;
y_ /= l;
}
};
void foo(mutable_vector& v) {
v.normalize();
}
void bar(vector& v) {
std::cout << v.length() << std::endl;
}
int main() {
mutable_vector my_vector(3.0, 4.0);
foo(my_vector);
bar(my_vector);
}
Looks fine. However, in a code review,
this would raise a huge laugh because C++ provides a much better solution.
Using the const
keyword,
one can let the compiler do the tedious work of providing two different interfaces!
It looks as follows:
#include <cmath>
#include <iostream>
class vector {
public:
vector(double x, double y): x_(x), y_(y) {}
double length() const {
return std::sqrt(x_ * x_ + y_ * y_);
}
void normalize() {
auto l = length();
x_ /= l;
y_ /= l;
}
private:
double x_;
double y_;
};
void foo(vector& v) {
v.normalize();
}
void bar(const vector& v) {
std::cout << v.length() << std::endl;
}
int main() {
vector my_vector(3.0, 4.0);
foo(my_vector);
bar(my_vector);
}
By marking vector::length
as const, but not vector::normalize
,
the compiler knows which member functions can be called
depending on the const
qualification of an instance or a reference to one.
bar
now takes a reference-to-const parameter,
which produces the exact effect we wanted,
i.e., v
is immutable and trying to call v.normalize()
in its body would result in a compile-time error:
void bar(const vector& v) {
v.normalize();
}
Error (generated by clang++
):
error: 'this' argument to member function 'normalize' has type 'const vector', but function is not marked const
v.normalize();
^
🎉
Good old C++, despite all its idiosyncrasies, seems to be doing something right here. :)
I'd love to hear your feedback.
Edit: The Kotlin community seems to already discuss such a possible feature (1, 2, 3), however, it seems to be quite tricky to integrate it into the language. I hope somebody finds a clean way, because I'd really love to have this in my day-to-day work with Kotlin.