Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do a cleanup pass on Core #971

Merged
merged 7 commits into from Aug 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 15 additions & 19 deletions modules/core/arrow-core/src/main/kotlin/arrow/core/Either.kt
Expand Up @@ -46,7 +46,7 @@ sealed class Either<out A, out B> : EitherOf<A, B> {
* @param ifRight the function to apply if this is a [Right]
* @return the results of applying the function
*/
inline fun <C> fold(crossinline ifLeft: (A) -> C, crossinline ifRight: (B) -> C): C = when (this) {
inline fun <C> fold(ifLeft: (A) -> C, ifRight: (B) -> C): C = when (this) {
is Right -> ifRight(b)
is Left -> ifLeft(a)
}
Expand All @@ -58,15 +58,15 @@ sealed class Either<out A, out B> : EitherOf<A, B> {
}

fun <C> foldLeft(initial: C, rightOperation: (C, B) -> C): C =
this.fix().let { either ->
fix().let { either ->
when (either) {
is Right -> rightOperation(initial, either.b)
is Left -> initial
}
}

fun <C> foldRight(initial: Eval<C>, rightOperation: (B, Eval<C>) -> Eval<C>): Eval<C> =
this.fix().let { either ->
fix().let { either ->
when (either) {
is Right -> rightOperation(either.b, initial)
is Left -> initial
Expand Down Expand Up @@ -94,11 +94,8 @@ sealed class Either<out A, out B> : EitherOf<A, B> {
* ```
*/
@Suppress("UNCHECKED_CAST")
inline fun <C> map(crossinline f: (B) -> C): Either<A, C> =
when (this) {
is Right -> Right(f(b))
is Left -> this
}
inline fun <C> map(f: (B) -> C): Either<A, C> =
flatMap { Right(f(it)) }

/**
* The given function is applied if this is a `Left`.
Expand All @@ -109,13 +106,13 @@ sealed class Either<out A, out B> : EitherOf<A, B> {
* Left(12).mapLeft { "flower" } // Result: Left("flower)
* ```
*/
inline fun <C> mapLeft(crossinline f: (A) -> C): Either<C, B> =
inline fun <C> mapLeft(f: (A) -> C): Either<C, B> =
fold({ Left(f(it)) }, { Right(it) })

/**
* Map over Left and Right of this Either
*/
inline fun <C, D> bimap(crossinline leftOperation: (A) -> C, crossinline rightOperation: (B) -> D): Either<C, D> =
inline fun <C, D> bimap(leftOperation: (A) -> C, rightOperation: (B) -> D): Either<C, D> =
fold({ Left(leftOperation(it)) }, { Right(rightOperation(it)) })

/**
Expand All @@ -131,7 +128,7 @@ sealed class Either<out A, out B> : EitherOf<A, B> {
* left.exists { it > 10 } // Result: false
* ```
*/
inline fun exists(crossinline predicate: (B) -> Boolean): Boolean =
fun exists(predicate: (B) -> Boolean): Boolean =
fold({ false }, { predicate(it) })

/**
Expand Down Expand Up @@ -211,8 +208,7 @@ fun <R> Right(right: R): Either<Nothing, R> = Either.right(right)
*
* @param f The function to bind across [Either.Right].
*/
@Suppress("UNCHECKED_CAST")
fun <A, B, C> EitherOf<A, B>.flatMap(f: (B) -> Either<A, C>): Either<A, C> =
inline fun <A, B, C> EitherOf<A, B>.flatMap(f: (B) -> Either<A, C>): Either<A, C> =
fix().let {
when (it) {
is Right -> f(it.b)
Expand All @@ -229,7 +225,7 @@ fun <A, B, C> EitherOf<A, B>.flatMap(f: (B) -> Either<A, C>): Either<A, C> =
* Left(12).getOrElse(17) // Result: 17
* ```
*/
inline fun <B> EitherOf<*, B>.getOrElse(crossinline default: () -> B): B =
inline fun <B> EitherOf<*, B>.getOrElse(default: () -> B): B =
fix().fold({ default() }, ::identity)

/**
Expand All @@ -242,7 +238,7 @@ inline fun <B> EitherOf<*, B>.getOrElse(crossinline default: () -> B): B =
* ```
*/
inline fun <B> EitherOf<*, B>.orNull(): B? =
getOrElse { null }
getOrElse { null }

/**
* Returns the value from this [Either.Right] or allows clients to transform [Either.Left] to [Either.Right] while providing access to
Expand All @@ -254,7 +250,7 @@ inline fun <B> EitherOf<*, B>.orNull(): B? =
* Left(12).getOrHandle { it + 5 } // Result: 17
* ```
*/
inline fun <A, B> EitherOf<A, B>.getOrHandle(crossinline default: (A) -> B): B =
inline fun <A, B> EitherOf<A, B>.getOrHandle(default: (A) -> B): B =
fix().fold({ default(it) }, ::identity)

/**
Expand All @@ -273,8 +269,8 @@ inline fun <A, B> EitherOf<A, B>.getOrHandle(crossinline default: (A) -> B): B =
* left.filterOrElse({ it > 10 }, { -1 }) // Result: Left(12)
* ```
*/
inline fun <A, B> EitherOf<A, B>.filterOrElse(crossinline predicate: (B) -> Boolean, crossinline default: () -> A): Either<A, B> =
fix().fold({ Left(it) }, { if (predicate(it)) Right(it) else Left(default()) })
inline fun <A, B> EitherOf<A, B>.filterOrElse(predicate: (B) -> Boolean, default: () -> A): Either<A, B> =
flatMap { if (predicate(it)) Right(it) else Left(default()) }

/**
* * Returns [Either.Right] with the existing value of [Either.Right] if this is an [Either.Right] with a non-null value.
Expand Down Expand Up @@ -316,7 +312,7 @@ fun <A, B, C> EitherOf<A, B>.ap(ff: EitherOf<A, (B) -> C>): Either<A, C> =
fun <A, B> EitherOf<A, B>.combineK(y: EitherOf<A, B>): Either<A, B> =
when (this) {
is Either.Left -> y.fix()
else -> this.fix()
else -> fix()
}

@Deprecated(DeprecatedAmbiguity, ReplaceWith("Try { body }.toEither()"))
Expand Down
2 changes: 1 addition & 1 deletion modules/core/arrow-core/src/main/kotlin/arrow/core/Id.kt
Expand Up @@ -15,7 +15,7 @@ data class Id<out A>(val value: A) : IdOf<A> {

fun <B> foldRight(initial: Eval<B>, operation: (A, Eval<B>) -> Eval<B>): Eval<B> = operation(this.fix().value, initial)

fun <B> coflatMap(f: (IdOf<A>) -> B): Id<B> = this.fix().map({ f(this) })
fun <B> coflatMap(f: (IdOf<A>) -> B): Id<B> = this.fix().map { f(this) }

fun extract(): A = this.fix().value

Expand Down
37 changes: 19 additions & 18 deletions modules/core/arrow-core/src/main/kotlin/arrow/core/Option.kt
Expand Up @@ -66,13 +66,8 @@ sealed class Option<out A> : OptionOf<A> {
* @param f the function to apply
* @see flatMap
*/
inline fun <B> map(crossinline f: (A) -> B): Option<B> = fold({ None }, { a -> Some(f(a)) })

inline fun <P1, R> map(p1: Option<P1>, crossinline f: (A, P1) -> R): Option<R> = if (isEmpty()) {
None
} else {
p1.map { pp1 -> f(get(), pp1) }
}
inline fun <B> map(f: (A) -> B): Option<B> =
flatMap { a -> Some(f(a)) }

inline fun <R> fold(ifEmpty: () -> R, ifSome: (A) -> R): R = when (this) {
is None -> ifEmpty()
Expand All @@ -89,26 +84,32 @@ sealed class Option<out A> : OptionOf<A> {
* @param f the function to apply
* @see map
*/
fun <B> flatMap(f: (A) -> OptionOf<B>): Option<B> = fold({ None }, { a -> f(a) }).fix()
inline fun <B> flatMap(f: (A) -> OptionOf<B>): Option<B> =
when (this) {
is None -> this
is Some -> f(t).fix()
}

fun <B> ap(ff: OptionOf<(A) -> B>): Option<B> = ff.fix().flatMap { this.fix().map(it) }
fun <B> ap(ff: OptionOf<(A) -> B>): Option<B> =
ff.fix().flatMap { this.fix().map(it) }

/**
* Returns this $option if it is nonempty '''and''' applying the predicate $p to
* this $option's value returns true. Otherwise, return $none.
*
* @param predicate the predicate used for testing.
*/
inline fun filter(crossinline predicate: Predicate<A>): Option<A> =
fold({ None }, { a -> if (predicate(a)) Some(a) else None })
fun filter(predicate: Predicate<A>): Option<A> =
flatMap { a -> if (predicate(a)) Some(a) else None }

/**
* Returns this $option if it is nonempty '''and''' applying the predicate $p to
* this $option's value returns false. Otherwise, return $none.
*
* @param predicate the predicate used for testing.
*/
inline fun filterNot(crossinline predicate: Predicate<A>): Option<A> = fold({ None }, { a -> if (!predicate(a)) Some(a) else None })
fun filterNot(predicate: Predicate<A>): Option<A> =
flatMap { a -> if (!predicate(a)) Some(a) else None }

/**
* Returns true if this option is nonempty '''and''' the predicate
Expand All @@ -117,31 +118,31 @@ sealed class Option<out A> : OptionOf<A> {
*
* @param predicate the predicate to test
*/
inline fun exists(crossinline predicate: Predicate<A>): Boolean = fold({ false }, { a -> predicate(a) })
fun exists(predicate: Predicate<A>): Boolean = fold({ false }, { a -> predicate(a) })

/**
* Returns true if this option is empty '''or''' the predicate
* $p returns true when applied to this $option's value.
*
* @param p the predicate to test
*/
inline fun forall(crossinline p: Predicate<A>): Boolean = fold({ true }, p)
fun forall(p: Predicate<A>): Boolean = fold({ true }, p)

@Deprecated(DeprecatedUnsafeAccess, ReplaceWith("fold({ Unit }, f)"))
inline fun forEach(f: (A) -> Unit) {
if (nonEmpty()) f(get())
}

fun <B> foldLeft(initial: B, operation: (B, A) -> B): B =
this.fix().let { option ->
fix().let { option ->
when (option) {
is Some -> operation(initial, option.t)
is None -> initial
}
}

fun <B> foldRight(initial: Eval<B>, operation: (A, Eval<B>) -> Eval<B>): Eval<B> =
this.fix().let { option ->
fix().let { option ->
when (option) {
is Some -> operation(option.t, initial)
is None -> initial
Expand All @@ -151,7 +152,7 @@ sealed class Option<out A> : OptionOf<A> {
fun <L> toEither(ifEmpty: () -> L): Either<L, A> =
fold({ ifEmpty().left() }, { it.right() })

fun toList(): List<A> = fold(::emptyList, { listOf(it) })
fun toList(): List<A> = fold(::emptyList) { listOf(it) }

infix fun <X> and(value: Option<X>): Option<X> = if (isEmpty()) {
None
Expand Down Expand Up @@ -190,7 +191,7 @@ fun <T> Option<T>.getOrElse(default: () -> T): T = fold({ default() }, ::identit
*
* @param alternative the default option if this is empty.
*/
fun <A, B : A> OptionOf<B>.orElse(alternative: () -> Option<B>): Option<B> = if (fix().isEmpty()) alternative() else fix()
inline fun <A, B : A> OptionOf<B>.orElse(alternative: () -> Option<B>): Option<B> = if (fix().isEmpty()) alternative() else fix()

infix fun <T> OptionOf<T>.or(value: Option<T>): Option<T> = if (fix().isEmpty()) {
value
Expand Down
43 changes: 23 additions & 20 deletions modules/core/arrow-core/src/main/kotlin/arrow/core/Try.kt
Expand Up @@ -2,7 +2,7 @@ package arrow.core

import arrow.higherkind

typealias Failure<A> = Try.Failure<A>
typealias Failure = Try.Failure
typealias Success<A> = Try.Success<A>

/**
Expand All @@ -21,7 +21,7 @@ sealed class Try<out A> : TryOf<A> {
tailrec fun <A, B> tailRecM(a: A, f: (A) -> TryOf<Either<A, B>>): Try<B> {
val ev: Try<Either<A, B>> = f(a).fix()
return when (ev) {
is Failure -> Failure<B>(ev.exception).fix()
is Failure -> Failure(ev.exception).fix()
is Success -> {
val b: Either<A, B> = ev.value
when (b) {
Expand Down Expand Up @@ -52,21 +52,23 @@ sealed class Try<out A> : TryOf<A> {
/**
* Returns the given function applied to the value from this `Success` or returns this if this is a `Failure`.
*/
inline fun <B> flatMap(crossinline f: (A) -> TryOf<B>): Try<B> = fold({ raise(it) }, { f(it).fix() })
inline fun <B> flatMap(f: (A) -> TryOf<B>): Try<B> =
when (this) {
is Failure -> this
is Success -> f(value).fix()
}

/**
* Maps the given function to the value from this `Success` or returns this if this is a `Failure`.
*/
inline fun <B> map(crossinline f: (A) -> B): Try<B> = fold({ Failure(it) }, { Success(f(it)) })
inline fun <B> map(f: (A) -> B): Try<B> =
flatMap { Success(f(it)) }

/**
* Converts this to a `Failure` if the predicate is not satisfied.
*/
inline fun filter(crossinline p: Predicate<A>): Try<A> =
fold(
{ Failure(it) },
{ if (p(it)) Success(it) else Failure(TryException.PredicateException("Predicate does not hold for $it")) }
)
fun filter(p: Predicate<A>): Try<A> =
flatMap { if (p(it)) Success(it) else Failure(TryException.PredicateException("Predicate does not hold for $it")) }

/**
* Inverts this `Try`. If this is a `Failure`, returns its exception wrapped in a `Success`.
Expand Down Expand Up @@ -126,19 +128,19 @@ sealed class Try<out A> : TryOf<A> {

fun toEither(): Either<Throwable, A> = fold({ Left(it) }, { Right(it) })

fun <B> foldLeft(initial: B, operation: (B, A) -> B): B = this.fix().fold({ initial }, { operation(initial, it) })
fun <B> foldLeft(initial: B, operation: (B, A) -> B): B = fix().fold({ initial }, { operation(initial, it) })

fun <B> foldRight(initial: Eval<B>, operation: (A, Eval<B>) -> Eval<B>): Eval<B> = this.fix().fold({ initial }, { operation(it, initial) })
fun <B> foldRight(initial: Eval<B>, operation: (A, Eval<B>) -> Eval<B>): Eval<B> = fix().fold({ initial }, { operation(it, initial) })

/**
* The `Failure` type represents a computation that result in an exception.
*/
data class Failure<out A>(val exception: Throwable) : Try<A>() {
data class Failure(val exception: Throwable) : Try<Nothing>() {
override fun isFailure(): Boolean = true

override fun isSuccess(): Boolean = false

override fun get(): A {
override fun get(): Nothing {
throw exception
}
}
Expand All @@ -155,7 +157,7 @@ sealed class Try<out A> : TryOf<A> {
}
}

sealed class TryException(override val message: String) : kotlin.Exception(message) {
sealed class TryException(override val message: String) : Exception(message) {
data class PredicateException(override val message: String) : TryException(message)
data class UnsupportedOperationException(override val message: String) : TryException(message)
}
Expand All @@ -165,22 +167,22 @@ sealed class TryException(override val message: String) : kotlin.Exception(messa
*
* ''Note:'': This will throw an exception if it is not a success and default throws an exception.
*/
fun <B> TryOf<B>.getOrDefault(default: () -> B): B = fix().fold({ default() }, ::identity)
inline fun <B> TryOf<B>.getOrDefault(default: () -> B): B = fix().fold({ default() }, ::identity)

/**
* Returns the value from this `Success` or the given `default` argument if this is a `Failure`.
*
* ''Note:'': This will throw an exception if it is not a success and default throws an exception.
*/
fun <B> TryOf<B>.getOrElse(default: (Throwable) -> B): B = fix().fold(default, ::identity)
inline fun <B> TryOf<B>.getOrElse(default: (Throwable) -> B): B = fix().fold(default, ::identity)

/**
* Returns the value from this `Success` or null if this is a `Failure`.
*/
fun <B> TryOf<B>.orNull(): B? = getOrElse { null }

fun <B, A : B> TryOf<A>.orElse(f: () -> TryOf<B>): Try<B> = when (this.fix()) {
is Try.Success -> this.fix()
inline fun <B, A : B> TryOf<A>.orElse(f: () -> TryOf<B>): Try<B> = when (fix()) {
is Try.Success -> fix()
is Try.Failure -> f().fix()
}

Expand All @@ -206,8 +208,9 @@ fun <A> TryOf<A>.handle(f: (Throwable) -> A): Try<A> = fix().recover(f)
* Completes this `Try` by applying the function `ifFailure` to this if this is of type `Failure`,
* or conversely, by applying `ifSuccess` if this is a `Success`.
*/
fun <A, B> TryOf<A>.transform(ifSuccess: (A) -> TryOf<B>, ifFailure: (Throwable) -> TryOf<B>): Try<B> = fix().fold({ ifFailure(it).fix() }, { fix().flatMap(ifSuccess) })
@Deprecated(DeprecatedAmbiguity, ReplaceWith("fold(ifFailure, ifSuccess)"))
inline fun <A, B> TryOf<A>.transform(ifSuccess: (A) -> TryOf<B>, ifFailure: (Throwable) -> TryOf<B>): Try<B> = fix().fold({ ifFailure(it).fix() }, { fix().flatMap(ifSuccess) })

fun <A> (() -> A).try_(): Try<A> = Try(this)

fun <T> TryOf<TryOf<T>>.flatten(): Try<T> = fix().flatMap(::identity)
fun <T> TryOf<TryOf<T>>.flatten(): Try<T> = fix().flatMap(::identity)
Expand Up @@ -35,4 +35,4 @@ class GetterSetterOperation<in K, V>(override val getter: (K) -> V, override val
SetterOperation<K, V>

const val DeprecatedUnsafeAccess: String = "This function is unsafe and will be removed in future versions of Arrow. Replace or import `arrow.syntax.unsafe.*` if you wish to continue using it in this way"
const val DeprecatedAmbiguity: String = "This function is ambiguous and will be removed in future versions of Arrow"
const val DeprecatedAmbiguity: String = "This function is ambiguous and will be removed in future versions of Arrow"
Expand Up @@ -97,9 +97,6 @@ class OptionTest : UnitSpec() {
"map" {
some.map(String::toUpperCase).get() shouldBe "KOTLIN"
none.map(String::toUpperCase) shouldBe None

some.map(Some(12)) { name, version -> "${name.toUpperCase()} M$version" }.get() shouldBe "KOTLIN M12"
none.map(Some(12)) { name, version -> "${name.toUpperCase()} M$version" } shouldBe None
}

"fold" {
Expand Down