Immutable means Immutable

In functional programming Immutable types are the norm. It is one of the strengths of the paradigm and is the reason why a lot of programmers are drawn to it. However take this data structure:

data class Name(val firstName: String, val lastName: String, val otherNames: String = "")

data class Address(val houseNumber: String, val streetName: String, val city: String)

data class Contact(val email: String, val telephoneNumber: String)

data class Person(val name: Name, val address: Address, val contact: Contact)

In order to change the telephone number of the Person we have to create a new Contact object and pass in the other non changed objects:

val changeTelephone = (person: Person, telephoneNumber: String): Person {
  Person(person.name, person.address, Contact(person.contact.email, telephoneNumber))
}

Which not only looks a little cumbersome, but involves a lot of boilerplate for every property of the Person class.

What if there was a way to expose some sort of Lense, or Optic into our data?

Arrow Optics1 is a functional way of fixing this problem. It creates a series of Lens which exposes functional references to the individual properties of the class.

So if we wished to edit the telephone number of a Person using Optics we would simply need:

@optics data class Name(val firstName: String, val lastName: String, val otherNames: String = "") { companion object }
@optics data class Address(val houseNumber: String, val streetName: String, val city: String) { companion object }
@optics data class Contact(val email: String, val telephoneNumber: String) { companion object }
@optics data class Person(val name: Name, val address: Address, val contact: Contact) { companion object }

Person.contact.telephone.modify(person) { telephoneNumber }

Removing excess boilerplate and maintaining the conciseness available to object orientated programming but in a functional way.

The code below allows us to only expose the mutator method to clients so only parts of our domain have access to changing the state of parts of the data:

class NameMutator(val person: Person) {
  fun modifyFirstName(val firstName: String): Person {
    Person.name.firstName.modify(person) { firstName }
  }
}

This can narrow the exposure of the data model in our domains and better encapsulate the data.