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

Remove dependency on kotlinx.coroutines for effects #250

Merged
merged 28 commits into from
Sep 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
736e092
Add initial implementation
Sep 2, 2017
16c54af
Merge remote-tracking branch 'origin/master' into paco-deferredkw
Sep 2, 2017
9a057d9
Fix unsafeRun
Sep 2, 2017
3319e4b
Fix tests
Sep 2, 2017
130e190
Move logged EQ to an extension function available only on the spec
Sep 2, 2017
e89f282
Simplify Eq with a new constructor
Sep 2, 2017
c492533
Remove redundant parameters
Sep 2, 2017
5980bec
Update documentation
Sep 2, 2017
5f2439d
Nit
Sep 2, 2017
a940a90
Fix async and tailrecM for DeferredKW
Sep 2, 2017
7ff893f
Fix async and tailrecM for DeferredKW
Sep 2, 2017
17a0754
Add a proper fixme
Sep 2, 2017
9f0aa0d
Fix detekt
Sep 3, 2017
759dabe
Remove map/flatMap dependency on CorutineContext
Sep 3, 2017
5d9a571
Fix tailrecM blocking the current thread
Sep 3, 2017
94bbfa0
Refactor DeferredKW to use CoroutineContext only when execution start…
Sep 3, 2017
4ed29e0
Remove redundant instance helpers
Sep 3, 2017
6219f67
Move CoroutineContext to be required only for evaluation. Add typecla…
Sep 3, 2017
a998b8f
Remove JobKW
Sep 3, 2017
534098f
Move kotlinx.coroutines to its own module
Sep 3, 2017
acaf2d6
Remove dependencies on kotlinx.coroutines
Sep 3, 2017
4bb9864
Remove bindingInContext. It didn't quite work unless it blocked.
Sep 3, 2017
5f3ae26
Comonad cobinding now allows setting its corutine context
Sep 3, 2017
7590a59
Fix detekt
Sep 3, 2017
114929c
Remove kategory-effects-kotlinx
Sep 13, 2017
23a43ae
Merge branch 'master' into paco-effectsfix
Sep 13, 2017
dc2a9ff
Fix merge conflicts
Sep 13, 2017
365a6ac
Fix detekt
Sep 13, 2017
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
1 change: 0 additions & 1 deletion kategory-core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlinVersion"
compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion"
compile project(':kategory-annotations')
kapt project(':kategory-annotations-processor')
kaptTest project(':kategory-annotations-processor')
Expand Down
8 changes: 5 additions & 3 deletions kategory-core/src/main/kotlin/kategory/data/Option.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ sealed class Option<out A> : OptionKind<A> {

/**
* Returns true if the option is [None], false otherwise.
* Used only for performance instead of fold.
* @note Used only for performance instead of fold.
*/
internal abstract val isEmpty: Boolean

/**
* Returns true if the option is an instance of $some, false otherwise.
* @note Used only for performance instead of fold.
*/
val isDefined: Boolean = !isEmpty
internal val isDefined: Boolean = !isEmpty

/**
* Returns a $some containing the result of applying $f to this $option's
Expand Down Expand Up @@ -142,8 +143,9 @@ sealed class Option<out A> : OptionKind<A> {
/**
* Returns false if the option is $none, true otherwise.
* @note Implemented here to avoid the implicit conversion to Iterable.
* @note Used only for performance instead of fold.
*/
val nonEmpty = isDefined
internal val nonEmpty = isDefined

/**
* Returns true if this option is nonempty '''and''' the predicate
Expand Down
11 changes: 3 additions & 8 deletions kategory-core/src/main/kotlin/kategory/typeclasses/Comonad.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package kategory

import java.io.Serializable
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.EmptyCoroutineContext
import kotlin.coroutines.experimental.RestrictsSuspension
import kotlin.coroutines.experimental.*
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
import kotlin.coroutines.experimental.startCoroutine

/**
* The dual of monads, used to extract values from F
Expand All @@ -27,9 +24,7 @@ inline fun <reified F, A> HK<F, A>.extract(FT: Comonad<F> = comonad()): A = FT.e
inline fun <reified F, A> HK<F, A>.duplicate(FT: Comonad<F> = comonad()): HK<F, HK<F, A>> = FT.duplicate(this)

@RestrictsSuspension
open class ComonadContinuation<F, A : Any>(val CM: Comonad<F>) : Serializable, Continuation<A> {

override val context = EmptyCoroutineContext
open class ComonadContinuation<F, A : Any>(val CM: Comonad<F>, override val context: CoroutineContext = EmptyCoroutineContext) : Serializable, Continuation<A> {

override fun resume(value: A) {
returnedMonad = value
Expand Down Expand Up @@ -59,7 +54,7 @@ open class ComonadContinuation<F, A : Any>(val CM: Comonad<F>) : Serializable, C
* A coroutine is initiated and inside `MonadContinuation` suspended yielding to `flatMap` once all the flatMap binds are completed
* the underlying monad is returned from the act of executing the coroutine
*/
fun <F, B : Any> Comonad<F>.cobinding(c: suspend ComonadContinuation<F, *>.() -> B): B {
fun <F, B : Any> Comonad<F>.cobinding(coroutineContext: CoroutineContext = EmptyCoroutineContext, c: suspend ComonadContinuation<F, *>.() -> B): B {
val continuation = ComonadContinuation<F, B>(this)
c.startCoroutine(continuation, continuation)
return continuation.returnedMonad
Expand Down
5 changes: 5 additions & 0 deletions kategory-core/src/main/kotlin/kategory/typeclasses/Eq.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ interface Eq<in F> : Typeclass {
fun neqv(a: F, b: F): Boolean = !eqv(a, b)

companion object {
inline operator fun <F> invoke(crossinline feqv: (F, F) -> Boolean): Eq<F> = object : Eq<F> {
override fun eqv(a: F, b: F): Boolean =
feqv(a, b)
}

fun any(): Eq<Any?> = EqAny

object EqAny : Eq<Any?> {
Expand Down
23 changes: 0 additions & 23 deletions kategory-core/src/main/kotlin/kategory/typeclasses/Monad.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package kategory

import kotlinx.coroutines.experimental.runBlocking
import java.io.Serializable
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.CoroutineContext
Expand Down Expand Up @@ -67,17 +66,6 @@ open class MonadContinuation<F, A>(M: Monad<F>, override val context: CoroutineC
COROUTINE_SUSPENDED
}

suspend fun <B> bindInContext(coroutineContext: CoroutineContext, m: suspend () -> HK<F, B>): B = suspendCoroutineOrReturn { c ->
val labelHere = c.stackLabels // save the whole coroutine stack labels
val result = runBlocking(coroutineContext) { m() }
returnedMonad = flatMap(result, { x: B ->
c.stackLabels = labelHere
c.resume(x)
returnedMonad
})
COROUTINE_SUSPENDED
}

infix fun <B> yields(b: B): HK<F, B> = yields { b }

infix fun <B> yields(b: () -> B): HK<F, B> = pure(b())
Expand Down Expand Up @@ -125,17 +113,6 @@ open class StackSafeMonadContinuation<F, A>(M: Monad<F>, override val context: C
COROUTINE_SUSPENDED
}

suspend fun <B> bindInContext(coroutineContext: CoroutineContext, m: suspend () -> Free<F, B>): B = suspendCoroutineOrReturn { c ->
val labelHere = c.stackLabels // save the whole coroutine stack labels
val freeResult = runBlocking(coroutineContext) { m() }
returnedMonad = freeResult.flatMap { z ->
c.stackLabels = labelHere
c.resume(z)
returnedMonad
}
COROUTINE_SUSPENDED
}

infix fun <B> yields(b: B): Free<F, B> = yields { b }

infix fun <B> yields(b: () -> B): Free<F, B> = Free.liftF(pure(b()))
Expand Down
4 changes: 1 addition & 3 deletions kategory-core/src/test/kotlin/kategory/data/CoproductTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package kategory

import io.kotlintest.KTestJUnitRunner
import io.kotlintest.matchers.shouldNotBe
import io.kotlintest.properties.forAll
import org.junit.runner.RunWith

@RunWith(KTestJUnitRunner::class)
class CoproductTest : UnitSpec() {
val EQ: Eq<HK3<CoproductHK, IdHK, IdHK, Int>> = object : Eq<HK3<CoproductHK, IdHK, IdHK, Int>> {
override fun eqv(a: CoproductKind<IdHK, IdHK, Int>, b: CoproductKind<IdHK, IdHK, Int>): Boolean =
val EQ: Eq<HK3<CoproductHK, IdHK, IdHK, Int>> = Eq { a, b ->
a.ev().extract() == b.ev().extract()
}

Expand Down
13 changes: 5 additions & 8 deletions kategory-core/src/test/kotlin/kategory/data/EitherTest.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package kategory

import io.kotlintest.KTestJUnitRunner
import io.kotlintest.matchers.shouldBe
import io.kotlintest.matchers.shouldNotBe
import io.kotlintest.properties.forAll
import kategory.Either.Left
Expand All @@ -10,6 +9,10 @@ import org.junit.runner.RunWith

@RunWith(KTestJUnitRunner::class)
class EitherTest : UnitSpec() {
val EQ: Eq<HK<EitherKindPartial<IdHK>, Int>> = Eq { a, b ->
a.ev() == b.ev()
}

init {

"instances can be resolved implicitly" {
Expand All @@ -24,13 +27,7 @@ class EitherTest : UnitSpec() {

testLaws(MonadErrorLaws.laws(Either.monadError(), Eq.any(), Eq.any()))
testLaws(TraverseLaws.laws(Either.traverse<Throwable>(), Either.applicative(), { it.right() }, Eq.any()))
testLaws(SemigroupKLaws.laws(
Either.semigroupK(),
Either.applicative(),
object : Eq<HK<EitherKindPartial<IdHK>, Int>> {
override fun eqv(a: HK<EitherKindPartial<IdHK>, Int>, b: HK<EitherKindPartial<IdHK>, Int>): Boolean =
a.ev() == b.ev()
}))
testLaws(SemigroupKLaws.laws(Either.semigroupK(), Either.applicative(), EQ))

"getOrElse should return value" {
forAll { a: Int, b: Int ->
Expand Down
3 changes: 1 addition & 2 deletions kategory-core/src/test/kotlin/kategory/data/EvalTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import org.junit.runner.RunWith
class EvalTest : UnitSpec() {
init {

testLaws(MonadLaws.laws(Eval.monad(), object : Eq<HK<EvalHK, Int>> {
override fun eqv(a: HK<EvalHK, Int>, b: HK<EvalHK, Int>): Boolean =
testLaws(MonadLaws.laws(Eval.monad(), Eq { a, b ->
a.ev().value() == b.ev().value()
}))

Expand Down
6 changes: 2 additions & 4 deletions kategory-core/src/test/kotlin/kategory/data/Function0Test.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package kategory

import io.kotlintest.KTestJUnitRunner
import io.kotlintest.matchers.shouldBe
import org.junit.runner.RunWith

@RunWith(KTestJUnitRunner::class)
class Function0Test : UnitSpec() {
val EQ: Eq<HK<Function0HK, Int>> = object : Eq<HK<Function0HK, Int>> {
override fun eqv(a: HK<Function0HK, Int>, b: HK<Function0HK, Int>): Boolean =
a() == b()
val EQ: Eq<HK<Function0HK, Int>> = Eq { a, b ->
a() == b()
}

init {
Expand Down
10 changes: 5 additions & 5 deletions kategory-core/src/test/kotlin/kategory/data/Function1Test.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package kategory

import io.kotlintest.KTestJUnitRunner
import io.kotlintest.matchers.shouldBe
import io.kotlintest.matchers.shouldNotBe
import org.junit.runner.RunWith

@RunWith(KTestJUnitRunner::class)
class Function1Test : UnitSpec() {
val EQ: Eq<Function1Kind<Int, Int>> = Eq { a, b ->
a(1) == b(1)
}

init {

"instances can be resolved implicitly" {
Expand All @@ -16,9 +19,6 @@ class Function1Test : UnitSpec() {
monadReader<Function1KindPartial<Int>, Int>() shouldNotBe null
}

testLaws(MonadLaws.laws(Function1.monad<Int>(), object : Eq<Function1Kind<Int, Int>> {
override fun eqv(a: Function1Kind<Int, Int>, b: Function1Kind<Int, Int>): Boolean =
a(1) == b(1)
}))
testLaws(MonadLaws.laws(Function1.monad<Int>(), EQ))
}
}
8 changes: 2 additions & 6 deletions kategory-core/src/test/kotlin/kategory/data/KleisliTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,8 @@ import org.junit.runner.RunWith

@RunWith(KTestJUnitRunner::class)
class KleisliTest : UnitSpec() {
private fun <A> EQ(): Eq<KleisliKind<TryHK, Int, A>> {
return object : Eq<KleisliKind<TryHK, Int, A>> {
override fun eqv(a: KleisliKind<TryHK, Int, A>, b: KleisliKind<TryHK, Int, A>): Boolean =
a.ev().run(1) == b.ev().run(1)

}
private fun <A> EQ(): Eq<KleisliKind<TryHK, Int, A>> = Eq { a, b ->
a.ev().run(1) == b.ev().run(1)
}

init {
Expand Down
27 changes: 13 additions & 14 deletions kategory-core/src/test/kotlin/kategory/data/OptionTTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import org.junit.runner.RunWith

@RunWith(KTestJUnitRunner::class)
class OptionTTest : UnitSpec() {
val EQ_ID: Eq<HK<OptionTKindPartial<IdHK>, Int>> = Eq { a, b ->
a.value() == b.value()
}

val NELM: Monad<NonEmptyListHK> = monad<NonEmptyListHK>()

init {

"instances can be resolved implicitly" {
Expand All @@ -20,51 +26,44 @@ class OptionTTest : UnitSpec() {
functorFilter<OptionTKindPartial<ListKWHK>>() shouldNotBe null
}

val OptionTFIdEq = object : Eq<HK<OptionTKindPartial<IdHK>, Int>> {
override fun eqv(a: HK<OptionTKindPartial<IdHK>, Int>, b: HK<OptionTKindPartial<IdHK>, Int>): Boolean =
a.ev().value == b.ev().value
}

testLaws(MonadLaws.laws(OptionT.monad(NonEmptyList.monad()), Eq.any()))
testLaws(TraverseLaws.laws(OptionT.traverse(), OptionT.applicative(Id.monad()), { OptionT(Id(it.some())) }, Eq.any()))
testLaws(SemigroupKLaws.laws(
OptionT.semigroupK(Id.monad()),
OptionT.applicative(Id.monad()),
OptionTFIdEq))
EQ_ID))

testLaws(MonoidKLaws.laws(
OptionT.monoidK(Id.monad()),
OptionT.applicative(Id.monad()),
OptionTFIdEq))
EQ_ID))

testLaws(FunctorFilterLaws.laws(
OptionT.functorFilter(),
{ OptionT(Id(it.some())) },
OptionTFIdEq))

val nelMonad = monad<NonEmptyListHK>()
EQ_ID))

"toLeft for Some should build a correct EitherT" {
forAll { a: Int, b: String ->
OptionT.fromOption<NonEmptyListHK, Int>(Option.Some(a)).toLeft({ b }, nelMonad) == EitherT.left<NonEmptyListHK, Int, String>(a, applicative())
OptionT.fromOption<NonEmptyListHK, Int>(Option.Some(a)).toLeft({ b }, NELM) == EitherT.left<NonEmptyListHK, Int, String>(a, applicative())
}
}

"toLeft for None should build a correct EitherT" {
forAll { a: Int, b: String ->
OptionT.fromOption<NonEmptyListHK, Int>(Option.None).toLeft({ b }, nelMonad) == EitherT.right<NonEmptyListHK, Int, String>(b, applicative())
OptionT.fromOption<NonEmptyListHK, Int>(Option.None).toLeft({ b }, NELM) == EitherT.right<NonEmptyListHK, Int, String>(b, applicative())
}
}

"toRight for Some should build a correct EitherT" {
forAll { a: Int, b: String ->
OptionT.fromOption<NonEmptyListHK, String>(Option.Some(b)).toRight({ a }, nelMonad) == EitherT.right<NonEmptyListHK, Int, String>(b, applicative())
OptionT.fromOption<NonEmptyListHK, String>(Option.Some(b)).toRight({ a }, NELM) == EitherT.right<NonEmptyListHK, Int, String>(b, applicative())
}
}

"toRight for None should build a correct EitherT" {
forAll { a: Int, b: String ->
OptionT.fromOption<NonEmptyListHK, String>(Option.None).toRight({ a }, nelMonad) == EitherT.left<NonEmptyListHK, Int, String>(a, applicative())
OptionT.fromOption<NonEmptyListHK, String>(Option.None).toRight({ a }, NELM) == EitherT.left<NonEmptyListHK, Int, String>(a, applicative())
}
}

Expand Down
3 changes: 1 addition & 2 deletions kategory-core/src/test/kotlin/kategory/data/OptionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ class OptionTest : UnitSpec() {
monadError<OptionHK, Unit>() shouldNotBe null
}

val EQ_EITHER: Eq<HK<OptionHK, Either<Unit, Int>>> = object : Eq<HK<OptionHK, Either<Unit, Int>>> {
override fun eqv(a: HK<OptionHK, Either<Unit, Int>>, b: HK<OptionHK, Either<Unit, Int>>): Boolean =
val EQ_EITHER: Eq<HK<OptionHK, Either<Unit, Int>>> = Eq { a, b ->
a.ev().fold(
{ b.ev().fold({ true }, { false }) },
{ eitherA: Either<Unit, Int> ->
Expand Down
40 changes: 17 additions & 23 deletions kategory-core/src/test/kotlin/kategory/data/StateTTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ import org.junit.runner.RunWith
@RunWith(KTestJUnitRunner::class)
class StateTTests : UnitSpec() {

val M: StateTMonadStateInstance<TryHK, Int> = StateT.monadState<TryHK, Int>(Try.monad())

val EQ: Eq<StateTKind<TryHK, Int, Int>> = Eq { a, b ->
a.runM(1, Try.monad()) == b.runM(1, Try.monad())
}

val EQ_UNIT: Eq<StateTKind<TryHK, Int, Unit>> = Eq { a, b ->
a.runM(1, Try.monad()) == b.runM(1, Try.monad())
}

val EQ_LIST: Eq<HK<StateTKindPartial<ListKWHK, Int>, Int>> = Eq { a, b ->
a.runM(1, ListKW.monad()) == b.runM(1, ListKW.monad())
}

init {

"instances can be resolved implicitly" {
Expand All @@ -17,35 +31,15 @@ class StateTTests : UnitSpec() {
semigroupK<StateTKindPartial<NonEmptyListHK, NonEmptyListHK>>() shouldNotBe null
}

val m: StateTMonadStateInstance<TryHK, Int> = StateT.monadState<TryHK, Int>(Try.monad())

testLaws(MonadStateLaws.laws(
m,
object : Eq<StateTKind<TryHK, Int, Int>> {
override fun eqv(a: StateTKind<TryHK, Int, Int>, b: StateTKind<TryHK, Int, Int>): Boolean =
a.runM(1, Try.monad()) == b.runM(1, Try.monad())

},
object : Eq<StateTKind<TryHK, Int, Unit>> {
override fun eqv(a: StateTKind<TryHK, Int, Unit>, b: StateTKind<TryHK, Int, Unit>): Boolean =
a.runM(1, Try.monad()) == b.runM(1, Try.monad())
}))

testLaws(MonadStateLaws.laws(M, EQ, EQ_UNIT))
testLaws(SemigroupKLaws.laws(
StateT.semigroupK<ListKWHK, Int>(ListKW.monad(), ListKW.semigroupK()),
StateT.applicative<ListKWHK, Int>(ListKW.monad()),
object : Eq<HK<StateTKindPartial<ListKWHK, Int>, Int>> {
override fun eqv(a: HK<StateTKindPartial<ListKWHK, Int>, Int>, b: HK<StateTKindPartial<ListKWHK, Int>, Int>): Boolean =
a.runM(1, ListKW.monad()) == b.runM(1, ListKW.monad())
}))

EQ_LIST))
testLaws(MonadCombineLaws.laws(StateT.monadCombine<ListKWHK, Int>(ListKW.monadCombine()),
{ StateT.lift(ListKW.pure(it), ListKW.monad()) },
{ StateT.lift(ListKW.pure({ s: Int -> s * 2 }), ListKW.monad()) },
object : Eq<HK<StateTKindPartial<ListKWHK, Int>, Int>> {
override fun eqv(a: HK<StateTKindPartial<ListKWHK, Int>, Int>, b: HK<StateTKindPartial<ListKWHK, Int>, Int>): Boolean =
a.runM(1, ListKW.monad()) == b.runM(1, ListKW.monad())
}))
EQ_LIST))

}
}
Loading