Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ allprojects {
mavenCentral()
}

version = "2.2.2"
version = "3.0.0"
group = "io.github.sphrak"
}

Expand Down
1 change: 1 addition & 0 deletions either/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ val inputFiles = project.fileTree(mapOf("dir" to "src", "include" to "**/*.kt"))
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
ktlint("com.pinterest:ktlint:0.40.0")
testImplementation("org.junit.jupiter:junit-jupiter:5.5.1")
testImplementation("org.assertj:assertj-core:3.13.2")
Expand Down
145 changes: 71 additions & 74 deletions either/src/main/java/io/github/sphrak/either/Either.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,124 +16,121 @@

package io.github.sphrak.either


/**
* [Either] is a monad or result type used to indicate two possible outcomes of a computation
*
* [Either.Right] and [Either.Left] can be used interchangeably to indicate a left or a right outcome
* of a computation.
* [Either] represents a value of one of two possible types (a disjoint union)
*
* By convention [Either.Left] is often used to indicate an fault or an erroneous computation result.
* Because of this methods like `onError`, `onSuccess`, `onResult` exists to allow for more human readable code
*
* @property [isRight] boolean value indicating whether instance is of type [Either.Right]
* @property [isLeft] boolean value indicating whether instance is of type [Either.Left]
* Instances of [Either] are either an instance of [Left] or [Right].
*/
public sealed class Either<out L, out R> {
public sealed interface Either<out L, out R> {

/**
* [Left] wrap a value [a] in [Either.Left]
*
* By convention is [Left] used for erroneous states
* Represents the left side of [Either] class, typically used for error handling.
*
* @param [a] of type [L] the left-hand side value
* @return [Either<L, Nothing>]
* @param a The value of the left side.
*/
data class Left<out L>(val a: L) : Either<L, Nothing>()
data class Left<out L>(val a: L) : Either<L, Nothing>

/**
* [Right] wrap a value [b] in [Either.Right]
*
* By convention is [Right] used for successful states
*
* @param [b] of type [R] the right-hand side value
* @return [Either<Nothing, R>]
* Represents the right side of [Either] class
* Mnemonically "Right" can be remembered as the "correct" or "right" outcome.
* @param b The value of the right side.
*/
data class Right<out R>(val b: R) : Either<Nothing, R>()
data class Right<out R>(val b: R) : Either<Nothing, R>

/**
* [isRight] A boolean value indicating whether [Either] is of type [Either.Right]
* Checks if the instance is of type [Right].
*
* @return `true` or `false`
* @return `true` if this is a [Right] instance, `false` otherwise.
*/
val isRight: Boolean get() = this is Right<R>

/**
* [isLeft] A boolean value indicating whether [Either] is of type [Either.Left]
* Checks if the instance is of type [Left].
*
* @return `true` or `false`
* @return `true` if this is a [Left] instance, `false` otherwise.
*/
val isLeft: Boolean get() = this is Left<L>

/**
* Creates a [Left] instance with the given value.
*
* @param a The value for the left side.
* @return An instance of [Left].
*/
fun <L> left(a: L): Left<L> = Left(a)

/**
* Creates a [Right] instance with the given value.
*
* @param b The value for the right side.
* @return An instance of [Right].
*/
fun <R> right(b: R): Right<R> = Right(b)

/**
* [either] resolve case [Either.Right] and case [Either.Left] by passing lambdas [fnL] and [fnR].
* Applies one of the given functions based on the type of this [Either].
*
* @receiver [Either.Left]
* @receiver [Either.Right]
* @param [fnL] lambda to execute when instance is of type Either.Left
* @param [fnR] lambda to execute when instance is of type Either.Right
* @param fnL Function to apply if this is a [Left].
* @param fnR Function to apply if this is a [Right].
*
* @return [T] value of [Either.Left<T>] or [Either.Right<T>]
* @return Result of applying the function.
*/
public inline fun <T> either(fnL: (L) -> T, fnR: (R) -> T): T =
fun <T> either(fnL: (L) -> T, fnR: (R) -> T): T =
when (this) {
is Left -> fnL(a)
is Right -> fnR(b)
}
}

internal fun <A, B, C> ((A) -> B).c(fn: (B) -> C): (A) -> C = {
fn(this(it))
}

internal suspend fun <A, B, C> (suspend (A) -> B).cSuspend(fn: suspend (B) -> C): suspend (A) -> C = {
fn(this(it))
}

/**
* [Either.flatMap] Gives access to value [R] in a lambda if the instance is of [Either.Right] and wraps
* and returns the resulting computation in the lambda in Either<L, R>
* Transforms the value of this [Either] using the given function, preserving the side.
*
* If the instance is [Either.Left] that is returned and lambda [fn] will not be executed.
* @param fnL Function to apply if this is a [Either.Left].
* @param fnR Function to apply if this is a [Either.Right].
*
* @receiver [Either.Right]
* @param [fn] lambda to be executed in case of [Either.Right<R>]
* @return Either<L, R>
* @return Transformed [Either] instance.
*/
public inline fun <T, L, R> Either<L, R>.flatMap(fn: (R) -> Either<L, T>): Either<L, T> =
when (this) {
is Either.Left -> this
is Either.Right -> fn(b)
}
public inline fun <T, L, R> Either<L, R>.flatMap(
fnL: ((L) -> Either<L, T>) = ::left,
fnR: (R) -> Either<L, T>
): Either<L, T> = when (this) {
is Either.Left -> fnL(a)
is Either.Right -> fnR(b)
}

/**
* [Either.flatMapSuspend] Suspending version of [flatMap]
* Transforms the value of this [Either] using the given function, preserving the side.
*
* Gives access to value [R] in a lambda if the instance is of [Either.Right] and wraps
* and returns the resulting computation in the lambda in Either<L, R>
*
* If the instance is [Either.Left] that is returned and lambda [fn] will not be executed.
*
* @receiver [Either.Right]
* @param [fn] lambda to be executed in case of [Either.Right<R>]
* @return Either<L, R>
* @param fnL Function to apply if this is a [Either.Left].
* @param fnR Function to apply if this is a [Either.Right].
* @return Transformed [Either] instance.
*/
public suspend inline fun <T, L, R> Either<L, R>.flatMapSuspend(fn: (R) -> Either<L, T>): Either<L, T> =
when (this) {
is Either.Left -> this
is Either.Right -> fn(b)
}
public inline fun <T, L, R> Either<L, R>.map(
fnL: (L) -> (L) = { left -> left },
fnR: (R) -> (T)
): Either<L, T> = when (this) {
is Either.Left -> fnL(this.a).asLeft()
is Either.Right -> fnR(this.b).asRight()
}

/**
* [Either.map] Gives access to value [R] in a lambda if the instance is of [Either.Right] and wraps
* and returns the resulting computation in the lambda in Either<L, R>
* Wraps a value into a [Either.Left] instance of [Either].
*
* If the instance is [Either.Left] that is returned and lambda [fn] will not be executed.
* @param T The type of the value.
* @return An instance of [Either.Left] containing the value.
*/
public fun <T> T.asLeft(): Either.Left<T> = Either.Left(this)

/**
* Wraps a value into a [Either.Right] instance of [Either].
*
* @receiver [Either.Right]
* @param [fn] lambda to be executed in case of [Either.Right<R>]
* @return Either<L, R>
* @param T The type of the value.
* @return An instance of [Either.Right] containing the value.
*/
public fun <T, L, R> Either<L, R>.map(fn: (R) -> (T)): Either<L, T> = this.flatMap(fn.c(::right))
public fun <T> T.asRight(): Either.Right<T> = Either.Right(this)


public val <L, R> Either<L, R>.rightOrNull get() = if (this is Either.Right<R>) this.b else null

public val <L, R> Either<L, R>.leftOrNull get() = if (this is Either.Left<L>) this.a else null
14 changes: 0 additions & 14 deletions either/src/main/java/io/github/sphrak/either/coroutines/Either.kt

This file was deleted.

132 changes: 1 addition & 131 deletions either/src/main/java/io/github/sphrak/either/extension/Either.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,142 +17,12 @@
package io.github.sphrak.either.extension

import io.github.sphrak.either.Either
import io.github.sphrak.either.c
import io.github.sphrak.either.flatMap

/**
* [asLeft] Convenience function to wrap a value type [T] in [Either.Left]
*
* @receiver [T]
* @return Either.Left<T>
*/
public fun <T> T.asLeft(): Either.Left<T> = Either.Left(this)

/**
* [asRight] Convenience function to wrap a value type [T] in [Either.Right]
*
* @receiver [T]
* @return [Either.Right<T>]
*/
public fun <T> T.asRight(): Either.Right<T> = Either.Right(this)

/**
* [onError] analogues to [Either.map] but for [Either.Left]
*
* @param [fn] lambda to execute in case of [Either.Right]
* @receiver [Either.Left]
* @return [Either]
*/
public fun <L, R> Either<L, R>.onError(fn: (L) -> Either<L, R>): Either<L, R> =
when (this) {
is Either.Left -> fn(a)
is Either.Right -> this
}

/**
* [onSuccess] analogues to [Either.map]
*
* @param [fn] lambda to execute in case of [Either.Right]
* @receiver [Either.Right]
* @return [Either]
*/
public fun <L, R> Either<L, R>.onSuccess(fn: (R) -> Either<L, R>): Either<L, R> =
when (this) {
is Either.Left -> this
is Either.Right -> fn(b)
}

/**
* [onResult] Resolve either case [Either.Right] or case [Either.Left]. This is analogues to [Either.either].
*
* @receiver [Either.Right]
* @receiver [Either.Left]
* @param [onError] function to execute when instance is Either.Left
* @param [onSuccess] function to execute when instance is Either.Right
*
* @return [T] value [T] of [Either.Left] or [Either.Right]
*/
public inline fun <T, L, R> Either<L, R>.onResult(onError: (L) -> T, onSuccess: (R) -> T): T =
when (this) {
is Either.Left -> onError(a)
is Either.Right -> onSuccess(b)
}

/**
* [onResultSuspend] Resolve either case [Either.Right] or case [Either.Left]. This is analogues to [eitherSuspend].
*
* @receiver [Either.Right]
* @receiver [Either.Left]
* @param [onError] function to execute when instance is Either.Left
* @param [onSuccess] function to execute when instance is Either.Right
*
* @return [T] value [T] of [Either.Left] or [Either.Right]
*/
public suspend fun <T, L, R> Either<L, R>.onResultSuspend(onError: suspend (L) -> T, onSuccess: suspend (R) -> T): T =
eitherSuspend(onError = onError, onSuccess = onSuccess)

/**
* [eitherSuspend] Resolve either case [Either.Right] or case [Either.Left]. This is analogues to [Either.either].
*
* @receiver [Either.Right]
* @receiver [Either.Left]
* @param [onError] function to execute when instance is Either.Left
* @param [onSuccess] function to execute when instance is Either.Right
*
* @return [T] value [T] of [Either.Left] or [Either.Right]
*/
public suspend fun <T, L, R> Either<L, R>.eitherSuspend(onError: suspend (L) -> T, onSuccess: suspend (R) -> T): T =
when (this) {
is Either.Left -> onError(a)
is Either.Right -> onSuccess(b)
}

/**
* [Either.mapSuspend] is a suspending version of [Either.map]
*
* Gives access to value [R] in a lambda if the instance is of [Either.Right] and wraps
* and returns the resulting computation in the lambda in Either<L, R>.
*
* If the instance is [Either.Left] that is returned and lambda [fn] will not be executed.
*
* @receiver [Either.Right]
* @param [fn] lambda to be executed in case of [Either.Right<R>]
* @return Either<L, R>
*/
public suspend fun <T, L, R> Either<L, R>.mapSuspend(fn: (R) -> (T)): Either<L, T> = this.flatMap(fn.c(::right))

/**
* [getRightOrNull] Access the value of [Either.Right] or `null`
*
* @return value of [R] or `null`
* @receiver [Either.Right]
*/
public inline val <L, R> Either<L, R>.getRightOrNull: R?
get() = (this as? Either.Right<R>)?.b

/**
* [successOrNull] Access the value of [Either.Right] or `null`. This is analogues to [getRightOrNull]
*
* @return value of [R] or `null`
* @receiver [Either.Right]
*/
public inline val <L, R> Either<L, R>.successOrNull: R?
get() = this.getRightOrNull

/**
* [getLeftOrNull] Access the value of [Either.Left] or `null`
*
* @return value of [L] or `null`
* @receiver [Either.Left]
*/
public inline val <L, R> Either<L, R>.getLeftOrNull: L?
get() = (this as? Either.Left<L>)?.a

/**
* [errorOrNull] Access the value of [Either.Left] or `null`. This is analogues to [getLeftOrNull]
*
* @return value of [L] or `null`
* @receiver [Either.Left]
*/
public inline val <L, R> Either<L, R>.errorOrNull: L?
get() = this.getLeftOrNull
}
Loading