DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Parallel Kafka Batch Processing With Kotlin Coroutines in Spring Boot
  • Why Kotlin Multiplatform is a Game-Changer for Startup Teams
  • Kotlin Code Style: Best Practices for Former Java Developers
  • Metal and the Simulated Annealing Algorithm

Trending

  • Testing Strategies for Web Development Code Generated by LLMs
  • The Hidden Cost of AI-Generated Frontend Code
  • Operationalizing Enterprise AI at Scale: Architecture, Governance, and Adoption
  • Reducing RAG Hallucinations With Relationship-Aware Retrieval
  1. DZone
  2. Coding
  3. Languages
  4. Deriving a Kotlin ''Try'' Type

Deriving a Kotlin ''Try'' Type

Time to dive into a Try or two.

By 
Biju Kunjummen user avatar
Biju Kunjummen
·
Mar. 17, 21 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
3.9K Views

Join the DZone community and get the full member experience.

Join For Free

Functional programming languages like Scala often have a type called "Try" to hold the result of a computation if successful or to capture an exception on failure. 

This is an incredibly useful type, allowing a caller to pointedly control how to handle an exceptional scenario. In this post, I will try and create such a type from scratch.

As an example, I will be using the scenario from Daniel Westheide's excellent introduction to the Try type in Scala

So my objective is to call a remote URL and return the content as a string. A few things can go wrong:

  • The URL can be badly formed
  • The URL may be wrong and may not have anything to retrieve content from

Let's start with the first one, the URL being badly formed, an API call using the "Try" type would look something like this:

Kotlin
 




x


 
1
fun parseUrl(url: String): Try<URL> {
2
    return Try.of {
3
        URL(url)
4
    }
5
}



Here a URL is being parsed and the result is a valid URL or an exception. So a Try type that can implement this much, would look something like this:

Kotlin
 




xxxxxxxxxx
1
15


 
1
sealed class Try<out T> {
2
 
          
3
    class Success<T>(private val result: T) : Try<T>()
4
 
          
5
    class Failure<T>(private val throwable: Throwable) : Try<T>()
6
 
          
7
 
          
8
    companion object {
9
        fun <T> of(block: () -> T) = try {
10
            Success(block())
11
        } catch (e: Throwable) {
12
            Failure(e)
13
        }
14
    }
15
}



"Try" type has two sub types - A "Success" wrapping a successful result and a "Failure" capturing an exception from the call. 

With the two subtypes in place, let's extend the use of the Try type:

Kotlin
 




xxxxxxxxxx
1


 
1
val urlResult: Try<URL> = parseUrl("htt://somewrongurl")
2
assertThat(urlResult.isFailure()).isTrue()
3
assertThat(urlResult.isSuccess()).isFalse()



If I were to call using a badly formatted URL like above with a wrong scheme "htt" instead of "http", should result in a failure. So let's implement the "isFailure" and "isSuccess" behavior:

Kotlin
 




xxxxxxxxxx
1
15


 
1
sealed class Try<out T> {
2
 
          
3
    abstract fun isSuccess(): Boolean
4
    fun isFailure(): Boolean = !isSuccess()
5
 
          
6
    class Success<T>(private val result: T) : Try<T>() {
7
        override fun isSuccess(): Boolean = true
8
    }
9
 
          
10
    class Failure<T>(private val throwable: Throwable) : Try<T>() {
11
        override fun isSuccess(): Boolean = false
12
    }
13
 
          
14
    ...
15
}



That works nicely, so now that a URL is available, hopefully valid, let's get some content from the URL:

Kotlin
 




xxxxxxxxxx
1


 
1
val uriResult: Try<URL> = parseUrl("http://someurl")
2
val getResult: Try<String> = getFromARemoteUrl(uriResult.get())
3
assertThat(getResult.get()).isEqualTo("a result")



which means that our "Try" type should have a "get()" method to retrieve the result if successful and can be implemented like this:

Kotlin
 




xxxxxxxxxx
1
14


 
1
sealed class Try<out T> {
2
    ...
3
    abstract fun get(): T
4
 
          
5
    class Success<T>(private val result: T) : Try<T>() {
6
        ...
7
        override fun get(): T = result
8
    }
9
 
          
10
    class Failure<T>(private val throwable: Throwable) : Try<T>() {
11
        ...
12
        override fun get(): T = throw throwable
13
    }
14
}



The Success path simply returns the result and the Failure path propagates the wrapped exception.

map Operation

Let's take it a small step forward. Given a URL, say you want to return the host of the URL

Kotlin
 




xxxxxxxxxx
1


 
1
val uriResult: Try<URL> = parseUrl("http://myhost")
2
assertThat(uriResult.get().host).isEqualTo("myhost")



While this works, the problem with the approach is that the "get()" call for an invalid URL would result in an exception if the URL is not valid to start with, so a better approach is to retrieve the host name only if the URL is valid. Traditionally this is done using a "map" operator and a usage looks like this:

Kotlin
 




xxxxxxxxxx
1


 
1
val urlResult: Try<URL> = parseUrl("http://myhost")
2
val hostResult: Try<String> = urlResult.map { url -> url.host }
3
assertThat(hostResult).isEqualTo(Try.success("myhost"))



So let's add in a "map" operator to the "Try" type:

Kotlin
 




xxxxxxxxxx
1
22


 
1
sealed class Try<out T> {
2
    ...
3
    abstract fun <R> map(block: (T) -> R): Try<R>
4
 
          
5
    abstract fun get(): T
6
 
          
7
    data class Success<T>(private val result: T) : Try<T>() {
8
        ...
9
        override fun <R> map(block: (T) -> R): Try<R> {
10
            return of {
11
                block(result)
12
            }
13
        }
14
    }
15
 
          
16
    data class Failure<T>(private val throwable: Throwable) : Try<T>() {
17
        ...
18
        override fun <R> map(block: (T) -> R): Try<R> {
19
            return this as Failure<R>
20
        }
21
    }
22
}



and it behaves as expected.

flatMap Operation

Along the lines of "map" operation, now let's get back to the original scenario of validating the URL and then attempting to get the content. Now the call to get content can also fail, so you would want that to be wrapped with a Try type also. 

Kotlin
 




xxxxxxxxxx
1


 
1
val urlResult: Try<URL> = parseUrl("http://someurl")
2
val getResult: Try<String> = getFromARemoteUrl(urlResult.get())



The two calls need to be chained together, and "map" operation may appear to be the right operator to use: 

Kotlin
 




xxxxxxxxxx
1


 
1
val urlResult: Try<URL> = parseUrl("http://someurl")
2
val getResult: Try<Try<String>> = urlResult.map { url -> getFromARemoteUrl(url) }



If you look at the response type now, it does not really line up, it is a "Try<Try<String>>" and not a "Try<String>", this is exactly what a flatMap operation does. It takes a valid URL and returns just the inner wrapped result. A test using it would look like this:

Kotlin
 




xxxxxxxxxx
1


 
1
val urlResult: Try<URL> = parseUrl("http://someurl")
2
val getResult: Try<String> = urlResult.flatMap { url -> getFromARemoteUrl(url) }
3
assertThat(getResult).isEqualTo(Try.success("a result"))



So how can "flatMap" be implemented? With a fairly simple code that looks like this:

Kotlin
 




xxxxxxxxxx
1
22


 
1
sealed class Try<out T> {
2
    ...
3
    abstract fun <R> flatMap(tryBlock: (T) -> Try<R>): Try<R>
4
 
          
5
    data class Success<T>(private val result: T) : Try<T>() {
6
        ...
7
        override fun <R> flatMap(tryBlock: (T) -> Try<R>): Try<R> {
8
            return try {
9
                tryBlock(result)
10
            } catch (e: Throwable) {
11
                failure(e)
12
            }
13
        }
14
    }
15
 
          
16
    data class Failure<T>(private val throwable: Throwable) : Try<T>() {
17
        ...
18
        override fun <R> flatMap(tryBlock: (T) -> Try<R>): Try<R> {
19
            return this as Failure<R>
20
        }
21
    }
22
}



One more small feature, given that Try type has two subtypes is to deconstruct the contents when required:

Kotlin
 




xxxxxxxxxx
1
12


 
1
val urlResult: Try<URL> = parseUrl("http://someurl")
2
val getResult: Try<String> = urlResult.flatMap { url -> getFromARemoteUrl(url) }
3
when (getResult) {
4
    is Try.Success -> {
5
        val (s) = getResult
6
        println("Got a clean result: $s")
7
    }
8
    is Try.Failure -> {
9
        val (e) = getResult
10
        println("An exception: $e")
11
    }
12
}



This assumes that the user knows the subtypes which may be an okay assumption to make for this type. 

Conclusion

A type like "Try" is incredibly useful in capturing a result cleanly or with exception and provides a neat alternative to using a normal try..catch block. Here I showed a way to write such a type from scratch, however, this may be overkill, a better way to get such a type is to simply use an excellent library like vavr which has the Try type already built-in. I feel it is instructive to create such a type from scratch though.

Here is the code in my GitHub repository - https://github.com/bijukunjummen/fp-experiment-kotlin/blob/master/src/main/kotlin/sample/adt/Try.kt

Kotlin (programming language)

Published at DZone with permission of Biju Kunjummen. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Parallel Kafka Batch Processing With Kotlin Coroutines in Spring Boot
  • Why Kotlin Multiplatform is a Game-Changer for Startup Teams
  • Kotlin Code Style: Best Practices for Former Java Developers
  • Metal and the Simulated Annealing Algorithm

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook