Functional constructs for better readability of Kotlin code

Vineeth Venudasan
4 min readMar 8, 2020

I happened to see this type being returned from long function call today, that set me down a rabbit hole

Try<CustomerInquiryResponse?>? 

But first, what is a Try?

It is a construct from the Arrow library, which is used to wrap the result of an operation to be either a value (if the operation was successful) or an Exception, if the operation raised an exception. Have a look at the documentation here for more information.

All good, but what caught my eyes was the fact that the Try was wrapping a nullable, and the Try object was itself nullable.

Let’s have a look at the code around this type to get a better idea

This, to me is more complicated than it needs to be. Much so because when I have a look at this type, I have no idea as to the different conditions under which

i) CustomerInquiryResponse is going to be null, and

ii) even less of an idea as to when Try<CustomerInquiryResponse?> itself is going to be null.

Code should scream intent. This one doesn’t.

What would I have liked better? A different Arrow construct, for a start

Either<Problem, CustomerInquiryResponse> 

An Either is another datatype provided by the Arrow library. It is a monad, that represents two possibilities, designated by a left and right side.

In the example above, on the right hand side, we have a CustomerInquiryResponse object, which is non-nullable in our case. This will be returned by the monad if the computation was successful.

On the left, we have a Problem which is a sealed class that would describe all the various issues that could happen when the customer inquiry response is computed.

This makes the contract extremely clear, which was lacking in the previous example, in my opinion.

But do we even have to use an external library?

Let’s first take a step back to Try with the nullables, let us see how the value stored in is unwrapped in, say the offer() method:

This makes a good revelation, which is that the author did not intend to differentiate between the cases where:

i) the customerInquiryResponse (the Try<>object) is null

ii) the inquiryResponse is null

iii) if the Try was wrapping a failure

In all three cases, customerId will be null, and the code will chug along. There is no separate logic for each of the three conditions.

Considering this, you could just written the code like this

Advice: Maybe you don’t always require to use a library, plain and simple Kotlin can sometimes do the job.

But let’s play along the route of having all the various Arrow constructs available to us.

Step one:

Remove nullability of the Try<> type. For this, I would need to know under what condition this type would be made null.

I saw that the Try<> was a nullable, because the method that created it

CustomerInquiryRequest.from(customerDetails) : Request? 

returns a null, if customerDetails was a null value.

I would first, turn this around, and make this method return a non-nullable Request instead of a Request? .

This implies that the check for the nullability of customerDetails must be extracted out of the method to the caller. The code will now look like this:

IMO, this looks better to me because the next reader of the code, can deduce that customerInquiryResponse will be null if the customerDetails just by the small amount of code above. That knowledge is no longer hidden away from view in the CustomerInquiryRequest.from(it) method.

This reduces cognitive load.

What if that is not always possible?

Taking the example above further, what if I have an intermediate method in the one of the .let{} blocks that simply had to return a null? The reader then cannot figure out if it was the request or any of the intermediate functions causes the return value to be null. In that case, I usually follow the paradigm that the Kotlin stdlib folk do by appending any method that returns a null with a orNull() in the method name. I would choose to be explicit about this.

Also, I prefer to handle all null conditions at the extremities and have all my other functions to take only non-nullable parameters, especially if they cannot handle nullables in the first place.

A variant of fail early, I would reckon.

Step 2

Now lets remove the Try and replace it with an Either

Changes for this needs to be done in two places:

i. Computation of customerInquiryResponse should respond with an Either.Left<Problem> if customerDetails is null

ii. The customerInquiryService.doInquiry() method should return an Either instead of a Try

This is how the final code looks

In conclusion

  1. Sometimes, changing the code structure will improve readability. Check if the next reader of the code has to hop through multiple methods to understand flow. Don’t pass a null to your functions if they cannot handle them.
  2. Use sealed classes to represent problems. In the case there are multiple actions that need to be taken for different problems, a sealed class ensures compile time checks that all conditions are handled.
  3. Be explicit about your code. Constructs like Either help you for this, instead of exception/null-checks.

Sign up to discover human stories that deepen your understanding of the world.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Written by Vineeth Venudasan

Backend Engineer / Software Consultant. The views expressed here are my own and does not necessarily represent my employer’s positions, strategies or opinions

Responses (1)

Write a response

Additionally Kotlin offers a Result, basically a Try, of its own now.

--