Kotlin 6
OK, let’s see if we can code up an operator in this baby. Much more comfort.
I think that if we allow for scaling Velocity and adding Velocity to a Coordinates to create new instances, the code will look better, and in the spirit of trying things, especially things that make for better code, I think I’ll give it a go.
Let’s begin with a test.
class VelocityTest {
@Test
fun scaling() {
val v = Velocity(100.0, 200.0)
val vScaled = v*0.25
assertThat(vScaled.dx).isEqualTo(25.0)
assertThat(vScaled.dy).isEqualTo(50.0)
}
}
I’m supposing that you can multiply a Velocity by a Double and get another Velocity back, scaled appropriately.
To make this work:
class Velocity(public var dx: Double = 0.0, public var dy: Double = 0.0) {
operator fun times(scale: Double) : Velocity {
return Velocity(dx*scale, dy*scale)
}
}
The operator times
is automatically used by Kotlin when one says, for some Velocity v, v*0.25
or the like. And the test passes.
I’d really like for the values in Velocity to be val
, rather than var
, so Velocity will be immutable. What happens when I change that?
class Velocity(public val dx: Double = 0.0, public val dy: Double = 0.0) {
operator fun times(scale: Double) : Velocity {
return Velocity(dx*scale, dy*scale)
}
}
My Ship code won’t compile because of the setters …
I am starting to wish I had been committing this code. I’ve broken something. I’ll see if I can back up.
OK, I am back to green with var
in those two places. Let’s get in the habit of committing. We can do that with IDEA, though my friends don’t like the option.
The commit button complains:
~/Dropbox/_2022_projects/ron-kotlin/src/main/kotlin/org/geepawhill/starter/Velocity.kt
Warning:(3, 16) Redundant visibility modifier
Warning:(3, 45) Redundant visibility modifier
~/Dropbox/_2022_projects/ron-kotlin/src/main/kotlin/org/geepawhill/starter/Coordinates.kt
Warning:(3, 19) Redundant visibility modifier
Warning:(3, 41) Redundant visibility modifier
~/Dropbox/_2022_projects/ron-kotlin/src/main/kotlin/org/geepawhill/starter/Ship.kt
Warning:(9, 20) Redundant setter parameter type
Warning:(12, 20) Redundant setter parameter type
I am not interested in those, so choose Commit Anyway.
Now let’s see if I can proceed more slowly. I’ll see if I can sort out those warnings, which probably refer to line and column.
In Velocity, IDEA hints to remove the public
modifiers, so I now have:
class Velocity(var dx: Double = 0.0, var dy: Double = 0.0) {
operator fun times(scale: Double) : Velocity {
return Velocity(dx*scale, dy*scale)
}
}
Am I green? Yes. Commit: remove public modifiers from Velocity.
But I really want those to be val. Let’s change just dx.
Kotlin objects now to this:
var dx
get() = velocity.dx
set(value) {velocity.dx = value}
And when I remove the set … it complains that I need a setter, so I must say:
val dx get() = velocity.dx
Now when I test something will surely break.
Yes, in Ship, “val cannot be reassigned”:
fun setVelocity(setDX: Double, setDY: Double) {
dx = setDX
dy = setDY
}
It is time to set our velocity directly. The sender of this method is here:
@Test
fun shipMoves() {
val ship = Ship(coords = Coordinates(0.0, 0.0))
ship.setVelocity(1000.0, 0.0)
ship.move(timeMS = 200.0)
assertThat(ship.x).isEqualTo(200.0)
assertThat(ship.y).isEqualTo(0.0)
}
Let’s fix setVelocity
for now. This should work, I think:
fun setVelocity(setDX: Double, setDY: Double) {
velocity = Velocity(setDX, setDY)
}
That works. Let’s do the other var, though we could commit now. It would be oddly unbalanced if we did.
class Velocity(val dx: Double = 0.0, val dy: Double = 0.0) {
operator fun times(scale: Double) : Velocity {
return Velocity(dx*scale, dy*scale)
}
}
val dy get() = velocity.dy
And we are green. Commit: Velocity objects are immutable.
Warnings are:
~/Dropbox/_2022_projects/ron-kotlin/src/main/kotlin/org/geepawhill/starter/Ship.kt
Warning:(9, 20) Redundant setter parameter type
Warning:(12, 20) Redundant setter parameter type
Those are the setters for x and y. I’m not sure what that means. the code is:
var x
get() = coords.x
set(value: Double) {coords.x = value}
var y
get() = coords.y
set(value: Double) {coords.y = value}
I mean, sure, the setters aren’t used, but are they unneccesary altogether? I wouldn’t have thought so.
Oh its the declaration of the type that it cares about. For now I dont mind, because I plan to make the Coordinates immutable as well.
Let’s proceed with that. I refactor this:
fun move(timeMS: Double) {
val scale = timeMS/1000.0
val scaledDX = dx*scale
val scaledDY = dy*scale
val newX = x + scaledDX
val newY = y + scaledDY
setCoords(newX, newY)
}
To this:
fun move(timeMS: Double) {
val scale = timeMS/1000.0
val scaled = velocity*scale
val newX = x + scaled.dx
val newY = y + scaled.dy
setCoords(newX, newY)
}
Green. And then to this:
fun move(timeMS: Double) {
val scale = timeMS/1000.0
val scaled = velocity*scale
val newPlace = Coordinates(x + scaled.dx, y + scaled.dy)
setCoords(newPlace.x, newPlace.y)
}
Green. And then to this:
fun move(timeMS: Double) {
val scale = timeMS/1000.0
val scaled = velocity*scale
val newPlace = Coordinates(x + scaled.dx, y + scaled.dy)
coords = newPlace
}
And remove the old setCoords
method. Still green.
Now I can and should remove the setters for x and y.
val x get() = coords.x
val y get() = coords.y
Now I can probably set the members of Coordinates class to be val
.
class Coordinates(val x: Double, val y: Double)
I removed the public
on the x and y the suggestion of IDEA.ic back and remove the getters?
Anyway green. Let’s commit: Coordinates are immutable.
I guess the creation parameters of an object are always accessible. I’ll have to look that up.
I think that’s enough tidying for the afternoon, it just turned 1600 and I started at 1430.
Let’s lay out all my code and sum up.
The Code
class ShipTest {
@Test
fun shipExists() {
val startingPos = Coordinates(100.0,200.0)
val s = Ship(startingPos)
assertThat(s.x).isEqualTo(100.0)
assertThat(s.y).isEqualTo(200.0)
assertThat(s.dx).isEqualTo(0.0)
assertThat(s.dy).isEqualTo(0.0)
}
@Test
fun shipMoves() {
val ship = Ship(coords = Coordinates(0.0, 0.0))
ship.setVelocity(1000.0, 0.0)
ship.move(timeMS = 200.0)
assertThat(ship.x).isEqualTo(200.0)
assertThat(ship.y).isEqualTo(0.0)
}
}
class Ship(var coords: Coordinates) {
var velocity = Velocity()
val x get() = coords.x
val y get() = coords.y
val dx get() = velocity.dx
val dy get() = velocity.dy
fun setVelocity(setDX: Double, setDY: Double) {
velocity = Velocity(setDX, setDY)
}
fun move(timeMS: Double) {
val scale = timeMS/1000.0
val scaled = velocity*scale
val newPlace = Coordinates(x + scaled.dx, y + scaled.dy)
coords = newPlace
}
}
class VelocityTest {
@Test
fun scaling() {
val v = Velocity(100.0, 200.0)
val vScaled = v*0.25
assertThat(vScaled.dx).isEqualTo(25.0)
assertThat(vScaled.dy).isEqualTo(50.0)
}
}
class Velocity(val dx: Double = 0.0, val dy: Double = 0.0) {
operator fun times(scale: Double) : Velocity {
return Velocity(dx*scale, dy*scale)
}
}
class Coordinates(val x: Double, val y: Double)
Summary
Well, this went even more smoothly, and I was even a bit distracted by being in another world, and by the cat requiring some attention. In my first few sessions with Kotlin and IDEA, I was so focused and so on edge that I couldn’t bear interruptions. This time, and this morning, it’s a bit less like running downhill too fast.
I am still not entirely happy with Kotlin and IDEA ganging up on me to be sure that all my types are in order. I’m used to working in a much more duck-typing mode. I suppose that if I really wanted that, I could use Any
a lot but that seems to violate the spirit of the language.
I’m left with just a small puzzlement … apparently with a class like Coordinates above, the creation member variables are always public. I’ll look that up later, in case there’s something else going on that I don’t recognize.
For now … getting smoother. As long as I don’t mess with gradle and maven, I might be OK.
See you tomorrow, gods willing.