Skip to content

Commit

Permalink
Add flatMap{Both,Either}
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbull committed Mar 8, 2024
1 parent 5d2c732 commit 4e5cdee
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 152 deletions.
201 changes: 134 additions & 67 deletions kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Map.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,86 +23,93 @@ public inline infix fun <V, E, U> Result<V, E>.map(transform: (V) -> U): Result<
}

/**
* Maps this [Result<V, E>][Result] to [Result<V, F>][Result] by either applying the [transform]
* function to the [error][Err.error] if this [Result] is [Err], or returning this [Ok].
* Maps this [Result<Result<V, E>, E>][Result] to [Result<V, E>][Result].
*
* - Elm: [Result.mapError](http://package.elm-lang.org/packages/elm-lang/core/latest/Result#mapError)
* - Haskell: [Data.Bifunctor.right](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Bifunctor.html#v:second)
* - Rust: [Result.map_err](https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err)
* - Rust: [Result.flatten](https://doc.rust-lang.org/std/result/enum.Result.html#method.flatten)
*/
public inline infix fun <V, E, F> Result<V, E>.mapError(transform: (E) -> F): Result<V, F> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

public inline fun <V, E> Result<Result<V, E>, E>.flatten(): Result<V, E> {
return when (this) {
is Ok -> this
is Err -> Err(transform(error))
is Ok -> value
is Err -> this
}
}

/**
* Maps this [Result<V, E>][Result] to [U] by either applying the [transform] function to the
* [value][Ok.value] if this [Result] is [Ok], or returning the [default] if this [Result] is an
* [Err].
* Maps this [Result<V, E>][Result] to [Result<U, E>][Result] by either applying the [transform]
* function if this [Result] is [Ok], or returning this [Err].
*
* - Rust: [Result.map_or](https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or)
* This is functionally equivalent to [andThen].
*
* - Scala: [Either.flatMap](http://www.scala-lang.org/api/2.12.0/scala/util/Either.html#flatMap[AA>:A,Y](f:B=>scala.util.Either[AA,Y]):scala.util.Either[AA,Y])
*/
public inline fun <V, E, U> Result<V, E>.mapOr(default: U, transform: (V) -> U): U {
public inline infix fun <V, E, U> Result<V, E>.flatMap(transform: (V) -> Result<U, E>): Result<U, E> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

return when (this) {
is Ok -> transform(value)
is Err -> default
}
return andThen(transform)
}

/**
* Maps this [Result<V, E>][Result] to [U] by applying either the [transform] function if this
* [Result] is [Ok], or the [default] function if this [Result] is an [Err]. Both of these
* functions must return the same type ([U]).
* Maps this [Result<V, E>][Result] to [U] by applying either the [success] function if this
* [Result] is [Ok], or the [failure] function if this [Result] is an [Err].
*
* - Rust: [Result.map_or_else](https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or_else)
* Unlike [mapEither], [success] and [failure] must both return [U].
*
* - Elm: [Result.Extra.mapBoth](http://package.elm-lang.org/packages/elm-community/result-extra/2.2.0/Result-Extra#mapBoth)
* - Haskell: [Data.Either.either](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:either)
*/
public inline fun <V, E, U> Result<V, E>.mapOrElse(default: (E) -> U, transform: (V) -> U): U {
public inline fun <V, E, U> Result<V, E>.mapBoth(
success: (V) -> U,
failure: (E) -> U,
): U {
contract {
callsInPlace(default, InvocationKind.AT_MOST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
callsInPlace(success, InvocationKind.AT_MOST_ONCE)
callsInPlace(failure, InvocationKind.AT_MOST_ONCE)
}

return when (this) {
is Ok -> transform(value)
is Err -> default(error)
is Ok -> success(value)
is Err -> failure(error)
}
}

/**
* Returns a [Result<List<U>, E>][Result] containing the results of applying the given [transform]
* function to each element in the original collection, returning early with the first [Err] if a
* transformation fails.
* Maps this [Result<V, E>][Result] to [U] by applying either the [success] function if this
* [Result] is [Ok], or the [failure] function if this [Result] is an [Err].
*
* Unlike [mapEither], [success] and [failure] must both return [U].
*
* This is functionally equivalent to [mapBoth].
*
* - Elm: [Result.Extra.mapBoth](http://package.elm-lang.org/packages/elm-community/result-extra/2.2.0/Result-Extra#mapBoth)
* - Haskell: [Data.Either.either](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:either)
*/
public inline infix fun <V, E, U> Result<Iterable<V>, E>.mapAll(transform: (V) -> Result<U, E>): Result<List<U>, E> {
return map { iterable ->
iterable.map { element ->
when (val transformed = transform(element)) {
is Ok -> transformed.value
is Err -> return transformed
}
}
public inline fun <V, E, U> Result<V, E>.fold(
success: (V) -> U,
failure: (E) -> U,
): U {
contract {
callsInPlace(success, InvocationKind.AT_MOST_ONCE)
callsInPlace(failure, InvocationKind.AT_MOST_ONCE)
}

return mapBoth(success, failure)
}

/**
* Maps this [Result<V, E>][Result] to [U] by applying either the [success] function if this
* [Result] is [Ok], or the [failure] function if this [Result] is an [Err]. Both of these
* functions must return the same type ([U]).
* Maps this [Result<V, E>][Result] to [Result<U, E>][Result] by applying either the [success]
* function if this [Result] is [Ok], or the [failure] function if this [Result] is an [Err].
*
* Unlike [mapEither], [success] and [failure] must both return [U].
*
* - Elm: [Result.Extra.mapBoth](http://package.elm-lang.org/packages/elm-community/result-extra/2.2.0/Result-Extra#mapBoth)
* - Haskell: [Data.Either.either](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:either)
*/
public inline fun <V, E, U> Result<V, E>.mapBoth(success: (V) -> U, failure: (E) -> U): U {
public inline fun <V, E, U> Result<V, E>.flatMapBoth(
success: (V) -> Result<U, E>,
failure: (E) -> Result<U, E>,
): Result<U, E> {
contract {
callsInPlace(success, InvocationKind.AT_MOST_ONCE)
callsInPlace(failure, InvocationKind.AT_MOST_ONCE)
Expand All @@ -115,65 +122,121 @@ public inline fun <V, E, U> Result<V, E>.mapBoth(success: (V) -> U, failure: (E)
}

/**
* Maps this [Result<V, E>][Result] to [U] by applying either the [success] function if this
* [Result] is [Ok], or the [failure] function if this [Result] is an [Err]. Both of these
* functions must return the same type ([U]).
* Maps this [Result<V, E>][Result] to [Result<U, F>][Result] by applying either the [success]
* function if this [Result] is [Ok], or the [failure] function if this [Result] is an [Err].
*
* This is functionally equivalent to [mapBoth].
* Unlike [mapBoth], [success] and [failure] may either return [U] or [F] respectively.
*
* - Elm: [Result.Extra.mapBoth](http://package.elm-lang.org/packages/elm-community/result-extra/2.2.0/Result-Extra#mapBoth)
* - Haskell: [Data.Either.either](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:either)
* - Haskell: [Data.Bifunctor.Bimap](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Bifunctor.html#v:bimap)
*/
public inline fun <V, E, U> Result<V, E>.fold(success: (V) -> U, failure: (E) -> U): U {
public inline fun <V, E, U, F> Result<V, E>.mapEither(
success: (V) -> U,
failure: (E) -> F,
): Result<U, F> {
contract {
callsInPlace(success, InvocationKind.AT_MOST_ONCE)
callsInPlace(failure, InvocationKind.AT_MOST_ONCE)
}

return mapBoth(success, failure)
return when (this) {
is Ok -> Ok(success(value))
is Err -> Err(failure(error))
}
}

/**
* Maps this [Result<V, E>][Result] to [Result<U, F>][Result] by applying either the [success]
* function if this [Result] is [Ok], or the [failure] function if this [Result] is an [Err].
*
* Unlike [mapBoth], [success] and [failure] may either return [U] or [F] respectively.
*
* - Haskell: [Data.Bifunctor.Bimap](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Bifunctor.html#v:bimap)
*/
public inline fun <V, E, U, F> Result<V, E>.mapEither(success: (V) -> U, failure: (E) -> F): Result<U, F> {
public inline fun <V, E, U, F> Result<V, E>.flatMapEither(
success: (V) -> Result<U, F>,
failure: (E) -> Result<U, F>,
): Result<U, F> {
contract {
callsInPlace(success, InvocationKind.AT_MOST_ONCE)
callsInPlace(failure, InvocationKind.AT_MOST_ONCE)
}

return when (this) {
is Ok -> Ok(success(value))
is Err -> Err(failure(error))
is Ok -> success(value)
is Err -> failure(error)
}
}

/**
* Maps this [Result<V, E>][Result] to [Result<U, E>][Result] by either applying the [transform]
* function if this [Result] is [Ok], or returning this [Err].
* Maps this [Result<V, E>][Result] to [Result<V, F>][Result] by either applying the [transform]
* function to the [error][Err.error] if this [Result] is [Err], or returning this [Ok].
*
* This is functionally equivalent to [andThen].
* - Elm: [Result.mapError](http://package.elm-lang.org/packages/elm-lang/core/latest/Result#mapError)
* - Haskell: [Data.Bifunctor.right](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Bifunctor.html#v:second)
* - Rust: [Result.map_err](https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err)
*/
public inline infix fun <V, E, F> Result<V, E>.mapError(transform: (E) -> F): Result<V, F> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

return when (this) {
is Ok -> this
is Err -> Err(transform(error))
}
}

/**
* Maps this [Result<V, E>][Result] to [U] by either applying the [transform] function to the
* [value][Ok.value] if this [Result] is [Ok], or returning the [default] if this [Result] is an
* [Err].
*
* - Scala: [Either.flatMap](http://www.scala-lang.org/api/2.12.0/scala/util/Either.html#flatMap[AA>:A,Y](f:B=>scala.util.Either[AA,Y]):scala.util.Either[AA,Y])
* - Rust: [Result.map_or](https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or)
*/
public inline infix fun <V, E, U> Result<V, E>.flatMap(transform: (V) -> Result<U, E>): Result<U, E> {
public inline fun <V, E, U> Result<V, E>.mapOr(default: U, transform: (V) -> U): U {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

return andThen(transform)
return when (this) {
is Ok -> transform(value)
is Err -> default
}
}

/**
* Maps this [Result<Result<V, E>, E>][Result] to [Result<V, E>][Result].
* Maps this [Result<V, E>][Result] to [U] by applying either the [transform] function if this
* [Result] is [Ok], or the [default] function if this [Result] is an [Err]. Both of these
* functions must return the same type ([U]).
*
* - Rust: [Result.flatten](https://doc.rust-lang.org/std/result/enum.Result.html#method.flatten)
* - Rust: [Result.map_or_else](https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or_else)
*/
public inline fun <V, E> Result<Result<V, E>, E>.flatten(): Result<V, E> {
return andThen { it }
public inline fun <V, E, U> Result<V, E>.mapOrElse(default: (E) -> U, transform: (V) -> U): U {
contract {
callsInPlace(default, InvocationKind.AT_MOST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

return when (this) {
is Ok -> transform(value)
is Err -> default(error)
}
}

/**
* Returns a [Result<List<U>, E>][Result] containing the results of applying the given [transform]
* function to each element in the original collection, returning early with the first [Err] if a
* transformation fails.
*/
public inline infix fun <V, E, U> Result<Iterable<V>, E>.mapAll(transform: (V) -> Result<U, E>): Result<List<U>, E> {
return map { iterable ->
iterable.map { element ->
when (val transformed = transform(element)) {
is Ok -> transformed.value
is Err -> return transformed
}
}
}
}

/**
Expand All @@ -194,6 +257,7 @@ public inline fun <V, E> Result<V, E>.toErrorIf(predicate: (V) -> Boolean, trans
} else {
this
}

is Err -> this
}
}
Expand All @@ -210,11 +274,12 @@ public inline fun <V, E> Result<V?, E>.toErrorIfNull(error: () -> E): Result<V,
}

return when (this) {
is Ok -> if(value == null) {
is Ok -> if (value == null) {
Err(error())
} else {
Ok(value)
}

is Err -> this
}
}
Expand All @@ -237,6 +302,7 @@ public inline fun <V, E> Result<V, E>.toErrorUnless(predicate: (V) -> Boolean, t
} else {
this
}

is Err -> this
}
}
Expand All @@ -258,6 +324,7 @@ public inline fun <V, E> Result<V, E>.toErrorUnlessNull(error: () -> E): Result<
} else {
Err(error())
}

is Err -> Err(error())
}
}
Loading

0 comments on commit 4e5cdee

Please sign in to comment.