Learning Kotlin: Operators don't need to mean one thing

Submitted by Robert MacLean on Tue, 07/31/2018 - 21:59

More Information

  • This is the 18th post in a multipart series.
    If you want to read more, see our series index

Following on from the previous post we looked at operators and being able to use them yourself by implementing the relevant operator methods. The first part I want to cover in this second post is the Unary operators +, -, and !.

When I was learning this, the term unary jumped out as one I did not immediately recognise, but a quick Wikipedia read it became clear. For example, if you use a negative unary with a positive number, it becomes a negative number... It is primary school maths with a fancy name.


One thing to remember about operators is it is totally up to you what they mean, so, for example, let's start with a simple pet class to allow us to define what type of pet we have.

  1. package blogcode
  2.  
  3. enum class animal {
  4.     dog,
  5.     cat
  6. }
  7.  
  8. data class pet(val type: animal);
  9.  
  10. fun main(args: Array<String>) {
  11.   val myPet = pet(animal.dog)
  12.   println(myPet)
  13. }

this produces pet(type=dog)

Now, maybe in my domain, the reverse of a dog is a cat, so I can do this to make this reflect my domain:

  1. package blogcode
  2.  
  3. enum class animal {
  4.     dog,
  5.     cat
  6. }
  7.  
  8. data class pet(val type: animal) {
  9.     operator fun not() : pet = when(this.type) {
  10.         animal.cat -> pet(animal.dog)
  11.         animal.dog -> pet(animal.cat)
  12.     }
  13. }
  14.  
  15. fun main(args: Array<String>) {
  16.   val myPet = pet(animal.dog)
  17.   println(!myPet)
  18. }

This produces pet(type=cat)

And this is the core thing, that while a Unary has a specific purpose normally you can totally use it the way that makes sense. This is really awesome and powerful but it doesn't stop there.

Normally when we think of something like the Unary not with a boolean, it goes from true to false (or vice versa), but it remains a boolean. There is nothing stating it has to be that way:

  1. package sadev
  2.  
  3. enum class animal {
  4.     dog,
  5.     cat
  6. }
  7.  
  8. data class pet(val type: animal) {
  9.     operator fun not() :String = "BEN"
  10. }
  11.  
  12. fun main(args: Array<String>) {
  13.   val myPet = pet(animal.dog)
  14.   val notPet = !myPet
  15.   println("myPet is ${myPet::class.simpleName} with a value of ${myPet}")
  16.   println("notPet is ${notPet::class.simpleName} with a value of ${notPet}")
  17. }

In the above, the output is

myPet is pet with a value of pet(type=dog)
notPet is String with a value of BEN

In short, the not of a dog pet is actually a string with the value of ben. I have no idea how this is useful to real development, but it is amazing that Kotlin is flexible enough to empower it.