How to compose independently computed Kotlin nullables, in an easy and clean way In Kotlin we have . null safety Despite nullable , there are many advantages to using it. Your API can express in a clean/compile-time-safe way, and bring back your function to the world. can’t be totally considered as a monad optionality side-effectful pure I find this feature as one of the most exciting and idiomatic of this language. Anyway, there is one drawback. Composability! I will propose a solution to this annoyance. Have you ever tried to compose nullables? Well… I can’t say that composing independently computed values really shines. Let’s figure out a real-world use-case. Crypto exchange Let’s think about a crypto exchange. Suppose that when registering, they ask you for your name, a username, and an email. Once registered you can start crawling the crypto world. You get excited about this world, and you want to start buying crypto. You can’t! Before you have to fill in your phone number, your credit card and complete the . All these are optional info, related to the user. KYC Let’s model it in our business logic: ( username: String, name: PersonalName, email: Email, phoneNumber: PhoneNumber? = , creditCard: CreditCard? = , kycVerification: KycVerificationData? = ) data class CryptoUser val val val val null val null val null The exchange has the procedure that, in order to let you buy crypto, requires those optional data. becomeRich ( username: String, phoneNumber: PhoneNumber, creditCard: CreditCard, kycVerification: KycVerificationData) = TODO( ) data class BuyCryptoInfo val val val val fun becomeRich (crypto: , buyInfo: ) CryptoInfo BuyCryptoInfo "conquer the world" Non-null from nullables Can you figure out a way to go from to ? CryptoUser BuyCryptoInfo You have two options, proposed as the following factory methods: ( username: String, phoneNumber: PhoneNumber, creditCard: CreditCard, kycVerification: KycVerificationData){ { : BuyCryptoInfo? = with(user){ (phoneNumber != && creditCard != && kycVerification != ) BuyCryptoInfo(username, phoneNumber, creditCard, kycVerification) } : BuyCryptoInfo? = with(user){ phoneNumber?.let { creditCard?.let { kycVerification?.let { BuyCryptoInfo(username, phoneNumber, creditCard, kycVerification) }}} } } } data class BuyCryptoInfo val val val val companion object fun from1 (user: ) CryptoUser if null null null else null fun from2 (user: ) CryptoUser Personally, I don’t like any of those solutions. But let’s continue. You click on the “ ” button, and suddenly you get… . buy Something mandatory is missing, please check your info With , we are only telling that something is missing but not what. Let’s change the code! nulls sealed { data { companion object{ fun from1(user: CryptoUser): Result<BuyCryptoInfo> = (user){ (phoneNumber == ) Result.Error( ) (creditCard == ) Result.Error( ) (kycVerification == ) Result.Error( ) Result.Ok(BuyCryptoInfo(username, phoneNumber, creditCard, kycVerification)) } } } < > class Result out T < >( : ): < >() ( : ): < >() } ( : , : , : , : ) class Ok out T val value T Result T data class Error val description String Result Nothing data class BuyCryptoInfo val username String val phoneNumber PhoneNumber val creditCard CreditCard val kycVerification KycVerificationData with if null return "missing phone number" if null return "missing credit card" if null return "missing kyc verification" return Perfect now we handle the errors! Let’s try again to buy! Click… . Fill in the phone… Buy! . Fill in the credit card… Buy! … missing phone missing credit card missing kyc verification Let’s change the code again… data { companion object{ fun from1(user: CryptoUser): Result<BuyCryptoInfo> = (user){ val listOfErrors = mutableListOf< >() (phoneNumber == ) listOfErrors.add( ) (creditCard == ) listOfErrors.add( ) (kycVerification == ) listOfErrors.add( ) (listOfErrors.size > ) Result.Error(listOfErrors.joinToString( )) Result.Ok(BuyCryptoInfo(username, phoneNumber!!, creditCard!!, kycVerification!!)) } } } ( : , : , : , : ) class BuyCryptoInfo val username String val phoneNumber PhoneNumber val creditCard CreditCard val kycVerification KycVerificationData with String if null "missing phone number" if null "missing credit card" if null "missing kyc verification" if 0 return "," return Now the user gets all the errors at once. And he is satisfied… but , as a developer? Are you satisfied as well? what about you Konad to the rescue! I was not happy at all with solutions like the one above. Then I started investigating for a better way. That was a long trip. I went through and Monads, Applicative Functors, Higher-Kinded types. In the end, I realized that what I was searching for for Kotlin. We have , but I think that it is an overkill for this specific purpose. And also, it has a steep learning curve for an OOP developer. didn’t exist yet Arrow Then I decided to build my own solution and finally, I came out with . Konad With Konad, the methods above become as follows: io.konad.* ( username: String, phoneNumber: PhoneNumber, creditCard: CreditCard, kycVerification: KycVerificationData){ { : Result<BuyCryptoInfo> = with(user){ ::BuyCryptoInfo.curry() .on(username) .on(phoneNumber.ifNull( )) .on(creditCard.ifNull( )) .on(kycVerification.ifNull( )) .result } : BuyCryptoInfo? = with(user){ ::BuyCryptoInfo.curry() .on(username) .on(phoneNumber.maybe) .on(creditCard.maybe) .on(kycVerification.maybe) .nullable } } } import data class BuyCryptoInfo val val val val companion object // You if you want to go for errors fun from1 (user: ) CryptoUser "missing phone number" "missing credit card" "missing kyc verification" // You if you want to go just for a null fun from2 (user: ) CryptoUser Hopefully, this can be considered pretty much cleaner than the previous proposals. Let’s finally see how to become rich! : = user = CryptoUser(username = , ..., ...) crypto = CryptoInfo( ) youGotRich1: Result< > = ::becomeRich.curry() .on(crypto) .on(BuyCryptoInfo.from1(user)) .result youGotRich2: Result< > = BuyCryptoInfo.from1(user) .map { cryptoInfo -> becomeRich(crypto, cryptoInfo) } (youGotRich1 ){ Result.Ok -> Result.Errors -> youGotRich1.description }.run(::println) fun becomeRich (crypto: , buyInfo: ) CryptoInfo BuyCryptoInfo Boolean false val "foo.bar" val "Bitcoin" val Boolean // or val Boolean when /*or youGotRich2*/ is "Congrats!" is // Will print Congrats! or the list of the missing information Conclusion Kotlin nullables are perfect to express the optionality of a computation. Anyway, they lack some UX in case of some more advanced use-cases. My hope is to have filled that gap with , and that you will enjoy using it. Konad Any feedback is much appreciated, as well as contributions to the library and bug or conceptual mistakes reporting. You can contact me on Twitter at @luca_picci or commenting the article. Thank you for reading.