Skip to content

Instantly share code, notes, and snippets.

@effe-megna
Last active October 21, 2019 20:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save effe-megna/e84f5fb313daa6c5484cc07f02813bbe to your computer and use it in GitHub Desktop.
Save effe-megna/e84f5fb313daa6c5484cc07f02813bbe to your computer and use it in GitHub Desktop.
Port of RemoteData to Kotlin
/**
*
* Port of [RemoteData](https://github.com/devexperts/remote-data-ts/blob/master/src/remote-data.ts)
*
* Represents a value of one of four possible types (a disjoint union)
* An instance of [RemoteData] is either an instance of [Initial], [Pending], [Failure] or [Success]
*
* A common use of [RemoteData] is as an alternative to [Either] or [Option] supporting initial and pending states
*
* Note: [Initial], [Pending] and [Failure] are commonly called "Left" part.
*/
@higherkind
sealed class RemoteData<out A, out B> : RemoteDataOf<A, B> {
/**
* The given function is applied if this is a `Success`
*
* Example:
* ```
* RemoteData.success(12).map { "flower" } // Result: Success("flower")
* RemoteData.failure(12).map { "flower" } // Result: Failure(12)
* ```
*/
fun <C> map(f: (B) -> C): RemoteData<A, C> =
flatMap { Success(f(it)) }
/**
* The given function is applied if this is a [Failure].
*
* Example:
* ```
* RemoteData.success(12).mapLeft { "flower" } // Result: Success(12)
* RemoteData.failure(12).mapLeft { "flower" } // Result: Failure("flower)
* ```
*/
fun <C> mapLeft(
f: (A) -> C
): RemoteData<C, B> =
fold(
{ initial() },
{ pending() },
{ failure(f(it)) },
{ success(it) }
)
/**
* Map over [Failure] and [Success] of this RemoteData
*/
fun <C, D> bimap(failureOperation: (A) -> C, successOperation: (B) -> D): RemoteData<C, D> =
fold(
{ initial() },
{ pending() },
{ failure(failureOperation(it)) },
{ success(successOperation(it)) }
)
/**
* Returns `false` if [Initial], [Pending] or [Failure], or returns the result of the application of
* the given predicate to the [Success] value.
*
* Example:
* ```
* RemoteData.success(12).exists { it > 10 } // Result: true
* RemoteData.success(7).exists { it > 10 } // Result: false
*
* val failure: RemoteData<Int, Int> = RemoteData.failure(12)
* failure.exists { it > 10 } // Result: false
*
* val initial: RemoteData<Int, Int> = RemoteData.initial()
* initial.exists { it > 12 } // Result: false
*
* val pending: RemoteData<Int, Int> = RemoteData.initial()
* pending.exists { it > 12 } // Result: false
* ```
*/
fun exists(predicate: (B) -> Boolean): Boolean =
fold({ false }, { false }, { false }, { predicate(it) })
/**
* If this is a `Failure`, then return the left value in `Success` or vice versa.
*
* Example
* ```
* RemoteData.failure("failure").swap() // Result // Success("failure")
* RemoteData.success("success").swap() // Result // Failure("success")
* ```
*/
fun swap(): RemoteData<B, A> =
fold({ initial() }, { pending() }, { success(it) }, { failure(it) })
/**
* Applies `ifInitial` if this is a [Initial]
* Applies `ifPending` if this is a [Pending]
* Applies `ifFailure` if this is a [Failure]
* Applies `ifSuccess` if this is a [Success]
*
* Example:
* ```
* val result: RemoteData<Exception, Value> = possiblyFailingOperation()
* result.fold(
* { log("initial state") },
* { log("pending state") },
* { log("operation failed with $it") },
* { log("operation succeeded with $it") }
* )
* ```
*
* @param ifInitial the function to apply if this is a [Initial]
* @param ifPending the function to apply if this is a [Pending]
* @param ifFailure the function to apply if this is a [Failure]
* @param ifSuccess the function to apply if this is a [Success]
*
* @return the results of applying the function
*/
inline fun <C> fold(
ifInitial: () -> C,
ifPending: () -> C,
ifFailure: (error: A) -> C,
ifSuccess: (value: B) -> C
): C = when (this) {
is Initial -> ifInitial()
is Pending -> ifPending()
is Failure -> ifFailure(error)
is Success -> ifSuccess(value)
}
/**
* Returns a [Some] containing the [Success] value
* if it exists or a [Failure] if this is a `Left`
*
* Example:
* ```
* RemoteData.success(12).toOption() // Result: Some(12)
* RemoteData.initial().toOption() // None
* RemoteData.pending().toOption() // None
* RemoteData.failure("failure").toOption() // None
* ```
*/
fun toOption(): Option<B> =
fold(
{ Option.empty() },
{ Option.empty() },
{ Option.empty() },
{ Option(it) }
)
/**
* The [Initial] side of the disjoint union.
*/
object Initial : RemoteData<Nothing, Nothing>()
/**
* The [Pending] side of the disjoint union.
*/
object Pending : RemoteData<Nothing, Nothing>()
/**
* The [Failure] side of the disjoint union.
*/
data class Failure<out A> internal constructor(val error: A) : RemoteData<A, Nothing>() {
companion object {
operator fun <A> invoke(a: A): RemoteData<A, Nothing> = Failure(a)
}
}
/**
* The [Success] side of the disjoint union, as opposed to the [Initial] [Pending] [Failure] side.
*/
data class Success<out B> internal constructor(val value: B) : RemoteData<Nothing, B>() {
companion object {
operator fun <B> invoke(b: B) : RemoteData<Nothing, B> = Success(b)
}
}
companion object {
fun initial(): RemoteData<Nothing, Nothing> = Initial
fun pending(): RemoteData<Nothing, Nothing> = Pending
fun <B> success(value: B): RemoteData<Nothing, B> = Success(value)
fun <A> failure(error: A): RemoteData<A, Nothing> = Failure(error)
}
}
fun <A, B, C> RemoteDataOf<A, B>.flatMap(f: (B) -> RemoteData<A, C>): RemoteData<A, C> =
fix().let {
when (it) {
is RemoteData.Initial -> it
is RemoteData.Pending -> it
is RemoteData.Success -> f(it.value)
is RemoteData.Failure -> it
}
}
fun <B> RemoteDataOf<*, B>.getOrElse(default: () -> B): B =
fix().fold( { default() }, { default() }, { default() }, ::identity)
fun <B> RemoteDataOf<*, B>.orNull(): B? =
getOrElse { null }
fun <A, B> RemoteDataOf<A, B>.fromOption(option: Option<B>, error: () -> A): RemoteData<A, B> =
option.fold(
{ RemoteData.failure(error()) },
{ RemoteData.success(it) }
)
fun <A, B> RemoteDataOf<A, B>.fromEither(either: Either<A, B>): RemoteDataOf<A, B> =
either.fold(
{ RemoteData.failure(it) },
{ RemoteData.success(it) }
)
fun <A, B> RemoteDataOf<A, B>.toEither(onInitial: () -> A, onPending: () -> A): Either<A, B> =
fix().fold(
{ Either.left(onInitial()) },
{ Either.left(onPending()) },
{ Either.left(it) },
{ Either.right(it) }
)
fun <A, B> B?.successIfNotNull(default: () -> A): RemoteData<A, B> = when (this) {
null -> RemoteData.failure(default())
else -> RemoteData.success(this)
}
fun <A, B> RemoteDataOf<A, B>.handleErrorWith(f: (A) -> RemoteDataOf<A, B>): RemoteData<A, B> =
fix().let {
when (it) {
is RemoteData.Initial -> it
is RemoteData.Pending -> it
is RemoteData.Failure -> f(it.error).fix()
is RemoteData.Success -> it
}
}
fun <A> A.initial(): RemoteData<Nothing, Nothing> = RemoteData.initial()
fun <A> A.pending(): RemoteData<Nothing, Nothing> = RemoteData.pending()
fun <A> A.failure(): RemoteData<A, Nothing> = RemoteData.failure(this)
fun <A> A.success(): RemoteData<Nothing, A> = RemoteData.success(this)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment