-
코틀린을 다루는 기술 7장 Result카테고리 없음 2020. 7. 25. 10:52
Option 만 쓰자니 정보의 제공에 한계가 있고,
Either<A,B> 를 쓰자니 결과의 성공, 실패라는 Context를 담기에는 부족한 면이 있어서 나온 것이
Result 이다.
Result는 Success, Failure, Empty 3가지의 구현체를 제공하는데
Failure, Empty의 차이는
Failure는 명시적인 실패의 이유를 담아서 리턴하고자 할 때,
Empty는 정상적인 케이스가 아니지만 없는 건처럼 취급하고자 할 때 사용한다.
sealed class Result<out A> : Serializable { abstract fun <B> map(f: (A) -> B): Result<B> abstract fun <B> flatMap(f: (A) -> Result<B>): Result<B> abstract fun mapFailure(message: String): Result<A> abstract fun forEach( onSuccess: (A) -> Unit = {}, onFailure: (RuntimeException) -> Unit = {}, onEmpty: () -> Unit = {} ) fun getOrElse(defaultValue: @UnsafeVariance A): A = when (this) { is Success -> this.value else -> defaultValue } fun getOrElse(defaultValue: () -> @UnsafeVariance A): A = when (this) { is Success -> this.value else -> defaultValue() } fun orElse(defaultValue: () -> Result<@UnsafeVariance A>): Result<A> = when (this) { is Success -> this else -> try { defaultValue() } catch (e: RuntimeException) { Failure<A>(e) } catch (e: Exception) { Failure<A>(RuntimeException(e)) } } fun filter(p: (A) -> Boolean): Result<A> = filter("Condition not matched", p) fun filter(message: String, p: (A) -> Boolean): Result<A> = flatMap { if (p(it)) this else Result.failure(message) } fun exists(p: (A) -> Boolean): Boolean = map(p).getOrElse(false) internal object Empty : Result<Nothing>() { override fun <B> map(f: (Nothing) -> B): Result<B> = Empty override fun <B> flatMap(f: (Nothing) -> Result<B>): Result<B> = Empty override fun mapFailure(message: String): Result<Nothing> = Empty override fun forEach( onSuccess: (Nothing) -> Unit, onFailure: (RuntimeException) -> Unit, onEmpty: () -> Unit ) = onEmpty() override fun toString(): String = "Empty" } internal class Failure<out A>(private val exception: RuntimeException) : Result<A>() { override fun <B> map(f: (A) -> B): Result<B> = Failure(exception) override fun <B> flatMap(f: (A) -> Result<B>): Result<B> = Failure(exception) override fun mapFailure(message: String): Result<A> = Failure(RuntimeException(message)) override fun forEach( onSuccess: (A) -> Unit, onFailure: (RuntimeException) -> Unit, onEmpty: () -> Unit ) = onFailure(exception) override fun toString(): String = "Failure(${exception.message})" } internal class Success<out A>(internal val value: A) : Result<A>() { override fun <B> map(f: (A) -> B): Result<B> = try { Success(f(value)) } catch (e: RuntimeException) { Failure(e) } catch (e: Exception) { Failure(RuntimeException(e)) } override fun <B> flatMap(f: (A) -> Result<B>): Result<B> = try { f(value) } catch (e: RuntimeException) { Failure(e) } catch (e: Exception) { Failure(RuntimeException(e)) } override fun mapFailure(message: String): Result<A> = this override fun forEach( onSuccess: (A) -> Unit, onFailure: (RuntimeException) -> Unit, onEmpty: () -> Unit ) = onSuccess(value) override fun toString(): String = "Success($value)" } companion object { operator fun <A> invoke(a: A? = null): Result<A> = when (a) { null -> Failure(NullPointerException()) else -> Success(a) } operator fun <A> invoke(): Result<A> = Empty fun <A> failure(message: String): Result<A> = Failure(IllegalStateException(message)) fun <A> failure(exception: RuntimeException): Result<A> = Failure(exception) fun <A> failure(exception: Exception): Result<A> = Failure(IllegalStateException(exception)) } }