/ EXERCISE, PROGRAMMING, STYLE

Exercises in Programming Style: maps are objects too

Last week’s post was dedicated to OOP. Despite popular belief, the exercise was solved using neither accessors i.e. getters and setters, nor shared mutable state.

The solution’s implementation was based on traditional OOP constructs offered by the Kotlin language: classes, inheritance and overriding. Other languages may offer different ways to do OOP.

This is the 8th post in the Exercises in Programming Style focus series.Other posts include:

  1. Introducing Exercises in Programming Style
  2. Exercises in Programming Style, stacking things up
  3. Exercises in Programming Style, Kwisatz Haderach-style
  4. Exercises in Programming Style, recursion
  5. Exercises in Programming Style with higher-order functions
  6. Composing Exercises in Programming Style
  7. Exercises in Programming Style, back to Object-Oriented Programming
  8. Exercises in Programming Style: maps are objects too (this post)
  9. Exercises in Programming Style: Event-Driven Programming
  10. Exercises in Programming Style and the Event Bus
  11. Reflecting over Exercises in Programming Style
  12. Exercises in Aspect-Oriented Programming Style
  13. Exercises in Programming Style: FP & I/O
  14. Exercises in Relational Database Style
  15. Exercises in Programming Style: spreadsheets
  16. Exercises in Concurrent Programming Style
  17. Exercises in Programming Style: sharing data among threads
  18. Exercises in Programming Style with Hazelcast
  19. Exercises in MapReduce Style
  20. Conclusion of Exercises in Programming Style

Objects in JavaScript

In JavaScript, an object is just a map whose values are either properties or functions.

var foo = {};              (1)
foo.bar = "bar";           (2)
foo.baz = function() {     (3)
  return this.bar;
};

console.log(foo.bar);
console.log(foo.baz());
1 Define a new object i.e. a map
2 Add a property
3 Add a function

We could write the equivalent code in Kotlin as the following:

val foo = mutableMapOf<String, Any>()
foo["bar"] = "bar";
foo["baz"] = { foo["bar"] }

println(foo["bar"])
println((foo["baz"] as () -> String)())

They both are quite similar, apart from the this reference in JavaScript. This is not possible in Kotlin, as there’s no way the lambda can reference objects other than by their name.

For this reason, the JavaScript version can be declared in the following concise way, while the Kotlin version cannot as we need a reference to the map:

var foo = {
  bar: "bar",
  baz: function() {
         return this.bar;
  }
};

Code sample

Here’s a sample to understand how the above snippet can be generalized to solve the assignment:

private const val DATA = "data"
private const val INIT = "init"
private const val WORDS = "words"

internal fun extractWords(obj: MutableMap<String, Any>, filename: String) {   (1)
    obj[DATA] = read(filename)
        .flatMap { it.split("\\W|_".toRegex()) }
        .filter { it.isNotBlank() && it.length >= 2 }
        .map(String::toLowerCase)
}

val dataStorageObj = mutableMapOf<String, Any>()
dataStorageObj[INIT] = { extractWords(dataStorageObj, filename) }            (2)
dataStorageObj[WORDS] = { dataStorageObj[DATA] }                             (2)

(dataStorageObj[INIT] as () -> Unit)()                                       (3)
1 Declare the function independently for readability purpose
2 Add functions to the map-object
3 Call the function

It’s also possible to use Kotlin’s extension functions feature:

internal fun MutableMap<String, Any>.extractWords(filename: String) {
    this[DATA] = read(filename)
        .flatMap { it.split("\\W|_".toRegex()) }
        .filter { it.isNotBlank() && it.length >= 2 }
        .map(String::toLowerCase)
}

val dataStorageObj = mutableMapOf<String, Any>()
dataStorageObj[INIT] = { dataStorageObj.extractWords(filename) }
dataStorageObj[WORDS] = { dataStorageObj[DATA] }

(dataStorageObj[INIT] as () -> Unit)()

The road to immutability

As a personal exercise, I tried to make the code immutable. The first step is to use a Map instead of MutableMap. In the first step, we need to reassign the reference each time, thus we end up using the var keyword instead of the val one:

internal fun Map<String, Any>.extractWords(filename: String) = this +
        (DATA to read(filename)
        .flatMap { it.split("\\W|_".toRegex()) }
        .filter { it.isNotBlank() && it.length >= 2 }
        .map(String::toLowerCase))

var dataStorageObj = mapOf<String, Any>()
dataStorageObj = dataStorageObj + (INIT to { dataStorageObj.extractWords(filename) })
dataStorageObj = dataStorageObj + (WORDS to { dataStorageObj[DATA] })

dataStorageObj = (dataStorageObj[INIT] as Callable<String, Any>)()

Now that’s done, we should initialize the object in one call chain, so we can again use an immutable reference:

val dataStorageObj = mapOf<String, Any>()
    .run { this + (INIT to { extractWords(filename) }) }
    .run { (this[INIT] as Callable<String, Any>)() }       (1)
    .run { this + (WORDS to { this[DATA] })}               (1)
1 The call order needs to be reversed

Note that I kept the word frequencies map mutable to benefit from the merge() method in my solution

Conclusion

As last week, no shared mutable state was involved.

Also, not all languages provide the same OOP implementation: some languages offer specific alternatives of it. In particular, JavaScript uses maps to implement objects. In this exercices, we mimicked this approach in Kotlin, with immutability as icing on the cake.

The complete source code for this post can be found on Github.
Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More