Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
November 25, 2021 07:34 pm GMT

Lenses: what they are and why you may want to use them ;)

This is my first post here, and I hope you will enjoy it.
I was trying to figure out which topic I can talk about and I came up with one I encounter in the last few weeks.

I am a backend software engineer in N26, Barcelona office. In N26 we use kotlin and we build microservices, as you can imagine, to run the digital bank.

We were working on a backoffice microservice, that somehow handled some data about our customers and exposed some endpoints to change them (like a CRUD but not exactly).

So I cannot tell the problem using the same data because those are privileged, but I can explain the same problem using a simpler example.

Suppose you have a model in your application to store information about a Person:

data class Person( val name: String,  val address: Address)data class Address( val streetName: String,  val number: String,  val city: String) 

Now imagine you have an endpoint and so a domain service to update information about the person, let's pretend you have an interface like:

interface UpdateAddressService { fun updateStreetName(   person: Person,    newStreetName: String ): Person}

Now if you try to implement such an interface using just kotlin language you can end up with a class like this:

class SimpleUpdateAddressService: UpdateAddressService { fun updateStreetName(   person: Person,    newStreetName: String ): Person {  val newAddress =     person.address.copy(streetName = newStreetName)  val updatedPerson =     person.copy(address = newAddress)  return updatedPerson }}

Now you can imagine that things can easily become cumbersome, imagine for example if the streetName was another value object, something like:

data class StreetName( val streetType: String, val name: String) 

then you would end up with something like:

class SimpleUpdateAddressService: UpdateAddressService { fun updateStreetName(   person: Person,    newStreetName: String ): Person {  val updatedStreetName =     person.address.streetName.copy(name = newStreetName)  val newAddress =     person.address.copy(streetName = updatedStreetName)  val updatedPerson =     person.copy(address = newAddress)  return updatedPerson }}

As you can see, as soon as your model starts to become nested the longer you will take to do the simple update you were required to.

There is a simple concept in functional programming, called Lens. A lens is a very simple interface that lets you get something from a source, let's call it a target, and let you also set a new target given a source value.

interface Lens<S, T> {  fun get(s: S): T  fun set(s: S, newT: T): S}

Now the real power as often happens in functional programming is that this structure supports what is called a Semigroup, meaning you can define an operation that takes two lenses and combines them, and also this operation is associative.

So for example you can define this combine method in the Lens interface like this:

interface Lens<S, T> {  fun get(s: S): T  fun set(newT: T, s: S): S  fun <A> combine(l2: Lens<T, A>): Lens<S, A> {    val self = this    return object : Lens<S, A> {      override fun get(s: S): A {        val function: (s: S) -> A = self::get andThen l2::get        return function(s)      }      override fun set(newT: A, s: S): S {        val newT1 = l2.set(newT, self.get(s))        return self.set(newT1, s)      }    }  }}

This is basically what arrow-kt provides to you without the need of having to define the Lens interface and all your needed lenses by yourself.

Arrow 1.0 uses kapt kotlin annotation processor, and using the @optics annotation you will be able to leverage the generated code at compile time using kapt like this:

@opticsdata class Person( val name:String,  val address: Address) { companion object}@opticsdata class Address( val streetName: String,  val number: String,  val city: String) { companion object}

And so with these newly defined data classes we can implement the previous interface in a different way:

class ArrowUpdateAddressService: UpdateAddressService { private val lens = Person.address.streetname fun updateStreetName(   person: Person,    newStreetName: String ): Person {    return lens.set(source = person, focus = newStreetName) }}

and in case we had the StreetName value object, we just need to change the lens:

class ArrowUpdateAddressService: UpdateAddressService { private val lens = Person.address.streetname.name // unchanged code

So you can see how powerful and clean this structure is.

I hope you enjoyed this article.

See you!

References

The image in the cover is taken from Wikimedia


Original Link: https://dev.to/itbhp/lenses-what-they-are-and-why-you-may-want-to-use-them--1op2

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To