Skip to content
This repository has been archived by the owner on Feb 24, 2021. It is now read-only.

Commit

Permalink
Add Semigroup and deconstruction to Ior (#205)
Browse files Browse the repository at this point in the history
* Initial naive implementation

* Removed monoid laws and added individual test cases

* Cleaned up ior and removed unecessary changes

* Add destructuring tests

* fixed formatting
  • Loading branch information
pablisco authored Aug 3, 2020
1 parent c9bbf85 commit c80db20
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 26 deletions.
56 changes: 48 additions & 8 deletions arrow-core-data/src/test/kotlin/arrow/core/IorTest.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package arrow.core

import arrow.core.extensions.component1
import arrow.core.extensions.component2
import arrow.core.extensions.either.eqK.eqK
import arrow.core.extensions.either.semigroupK.semigroupK
import arrow.core.extensions.eq
import arrow.core.extensions.hash
import arrow.core.extensions.id.eq.eq
import arrow.core.extensions.ior.applicative.applicative
import arrow.core.extensions.ior.bicrosswalk.bicrosswalk
import arrow.core.extensions.ior.bifunctor.bifunctor
Expand All @@ -13,13 +18,15 @@ import arrow.core.extensions.ior.eqK2.eqK2
import arrow.core.extensions.ior.functor.functor
import arrow.core.extensions.ior.hash.hash
import arrow.core.extensions.ior.monad.monad
import arrow.core.extensions.ior.semigroup.semigroup
import arrow.core.extensions.ior.show.show
import arrow.core.extensions.ior.traverse.traverse
import arrow.core.extensions.semigroup
import arrow.core.extensions.show
import arrow.core.test.UnitSpec
import arrow.core.test.generators.genK
import arrow.core.test.generators.genK2
import arrow.core.test.generators.id
import arrow.core.test.generators.ior
import arrow.core.test.laws.BicrosswalkLaws
import arrow.core.test.laws.BifunctorLaws
Expand All @@ -28,10 +35,12 @@ import arrow.core.test.laws.CrosswalkLaws
import arrow.core.test.laws.EqK2Laws
import arrow.core.test.laws.HashLaws
import arrow.core.test.laws.MonadLaws
import arrow.core.test.laws.SemigroupKLaws
import arrow.core.test.laws.SemigroupLaws
import arrow.core.test.laws.ShowLaws
import arrow.core.test.laws.TraverseLaws
import arrow.typeclasses.Eq
import arrow.typeclasses.Monad
import io.kotlintest.forAll
import io.kotlintest.properties.Gen
import io.kotlintest.properties.forAll
import io.kotlintest.shouldBe
Expand All @@ -42,12 +51,14 @@ class IorTest : UnitSpec() {

val intIorMonad: Monad<IorPartialOf<Int>> = Ior.monad(Int.semigroup())

val EQ = Ior.eq(Eq.any(), Eq.any())
val EQ = Ior.eq(String.eq(), Int.eq())
val GEN = Gen.ior(Gen.string(), Gen.int())

testLaws(
EqK2Laws.laws(Ior.eqK2(), Ior.genK2()),
BifunctorLaws.laws(Ior.bifunctor(), Ior.genK2(), Ior.eqK2()),
ShowLaws.laws(Ior.show(String.show(), Int.show()), EQ, Gen.ior(Gen.string(), Gen.int())),
ShowLaws.laws(Ior.show(String.show(), Int.show()), EQ, GEN),
SemigroupLaws.laws(Ior.semigroup(String.semigroup(), Int.semigroup()), GEN, EQ),
MonadLaws.laws(
Ior.monad(Int.semigroup()),
Ior.functor(),
Expand All @@ -56,13 +67,10 @@ class IorTest : UnitSpec() {
Ior.genK(Gen.int()),
Ior.eqK(Int.eq())
),
TraverseLaws.laws(Ior.traverse(),
Ior.applicative(Int.semigroup()),
Ior.genK(Gen.int()),
Ior.eqK(Int.eq())
),
TraverseLaws.laws(Ior.traverse(), Ior.applicative(Int.semigroup()), Ior.genK(Gen.int()), Ior.eqK(Int.eq())),
HashLaws.laws(Ior.hash(String.hash(), Int.hash()), Gen.ior(Gen.string(), Gen.int()), Ior.eq(String.eq(), Int.eq())),
BitraverseLaws.laws(Ior.bitraverse(), Ior.genK2(), Ior.eqK2()),
SemigroupKLaws.laws(Either.semigroupK(), Either.genK(Gen.id(Gen.int())), Either.eqK(Id.eq(Int.eq()))),
CrosswalkLaws.laws(Ior.crosswalk(), Ior.genK(Gen.int()), Ior.eqK(Int.eq())),
BicrosswalkLaws.laws(Ior.bicrosswalk(), Ior.genK2(), Ior.eqK2())
)
Expand Down Expand Up @@ -191,5 +199,37 @@ class IorTest : UnitSpec() {
val iorResult = intIorMonad.run { ior1.flatMap { Ior.Left(7) } }
iorResult shouldBe Ior.Left(10)
}

"combine cases for Semigroup" {
fun case(a: Ior<String, Int>, b: Ior<String, Int>, result: Ior<String, Int>) = listOf(a, b, result)
Ior.semigroup(String.semigroup(), Int.semigroup()).run {
forAll(listOf(
case("Hello, ".leftIor(), Ior.Left("Arrow!"), Ior.Left("Hello, Arrow!")),
case(Ior.Left("Hello"), Ior.Right(2020), Ior.Both("Hello", 2020)),
case(Ior.Left("Hello, "), Ior.Both("number", 1), Ior.Both("Hello, number", 1)),
case(Ior.Right(9000), Ior.Left("Over"), Ior.Both("Over", 9000)),
case(Ior.Right(9000), Ior.Right(1), Ior.Right(9001)),
case(Ior.Right(8000), Ior.Both("Over", 1000), Ior.Both("Over", 9000)),
case(Ior.Both("Hello ", 1), Ior.Left("number"), Ior.Both("Hello number", 1)),
case(Ior.Both("Hello number", 1), Ior.Right(1), Ior.Both("Hello number", 2)),
case(Ior.Both("Hello ", 1), Ior.Both("number", 1), Ior.Both("Hello number", 2))
)) { (a, b, expectedResult) ->
a + b shouldBe expectedResult
}
}
}

"destructuring declarations" {
data class Case(val ior: Ior<String, Int>, val left: String?, val right: Int?)
forAll(listOf(
Case(Ior.Left("Hey!"), "Hey!", null),
Case(Ior.Right(2020), null, 2020),
Case(Ior.Both("Hey!", 2020), "Hey!", 2020)
)) { (ior, expectedLeft, expectedRight) ->
val (actualLeft, actualRight) = ior
actualLeft shouldBe expectedLeft
actualRight shouldBe expectedRight
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ import arrow.core.Tuple7
import arrow.core.Tuple8
import arrow.core.Tuple9
import arrow.core.Validated
import arrow.core.extensions.sequence.functorFilter.filterMap
import arrow.core.extensions.sequencek.apply.apply
import arrow.core.extensions.sequencek.functorFilter.filterMap
import arrow.core.extensions.listk.semialign.semialign
import arrow.core.extensions.sequencek.semialign.semialign
import arrow.core.fix
import arrow.core.k
import arrow.core.toOption
import arrow.typeclasses.Applicative
Expand Down Expand Up @@ -178,21 +178,7 @@ fun <T> Gen.Companion.id(gen: Gen<T>): Gen<Id<T>> = object : Gen<Id<T>> {
}

fun <A, B> Gen.Companion.ior(genA: Gen<A>, genB: Gen<B>): Gen<Ior<A, B>> =
object : Gen<Ior<A, B>> {
override fun constants(): Iterable<Ior<A, B>> =
(genA.orNull().constants().asSequence().k() to genB.orNull().constants().asSequence().k()).let { (ls, rs) ->
SequenceK.apply().run { ls.product(rs) }.filterMap {
Ior.fromOptions(Option.fromNullable(it.a), Option.fromNullable(it.b))
}.asIterable()
}

override fun random(): Sequence<Ior<A, B>> =
(Gen.option(genA).random() to Gen.option(genB).random()).let { (ls, rs) ->
ls.zip(rs).filterMap {
Ior.fromOptions(it.first, it.second)
}
}
}
genA.alignWith(genB) { it }

fun <A, B> Gen.Companion.genConst(gen: Gen<A>): Gen<Const<A, B>> =
gen.map {
Expand All @@ -204,3 +190,16 @@ fun <A> Gen<A>.eval(): Gen<Eval<A>> =

fun Gen.Companion.char(): Gen<Char> =
Gen.from(('A'..'Z') + ('a'..'z') + ('0'..'9') + "!@#$%%^&*()_-~`,<.?/:;}{][±§".toList())

private fun <A, B, R> Gen<A>.alignWith(genB: Gen<B>, transform: (Ior<A, B>) -> R): Gen<R> =
object : Gen<R> {
override fun constants(): Iterable<R> =
ListK.semialign().run {
alignWith(this@alignWith.constants().toList().k(), genB.constants().toList().k(), transform)
}.fix()

override fun random(): Sequence<R> =
SequenceK.semialign().run {
alignWith(this@alignWith.random().k(), genB.random().k(), transform)
}.fix()
}
33 changes: 33 additions & 0 deletions arrow-core/src/main/kotlin/arrow/core/extensions/ior.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,36 @@ import arrow.typeclasses.Show
import arrow.typeclasses.Traverse
import arrow.undocumented

@extension
interface IorSemigroup<L, R> : Semigroup<Ior<L, R>> {

fun SGL(): Semigroup<L>
fun SGR(): Semigroup<R>

override fun Ior<L, R>.combine(b: Ior<L, R>): Ior<L, R> =
with(SGL()) {
with(SGR()) {
when (val a = this@combine) {
is Ior.Left -> when (b) {
is Ior.Left -> Ior.Left(a.value + b.value)
is Ior.Right -> Ior.Both(a.value, b.value)
is Ior.Both -> Ior.Both(a.value + b.leftValue, b.rightValue)
}
is Ior.Right -> when (b) {
is Ior.Left -> Ior.Both(b.value, a.value)
is Ior.Right -> Ior.Right(a.value + b.value)
is Ior.Both -> Ior.Both(b.leftValue, a.value + b.rightValue)
}
is Ior.Both -> when (b) {
is Ior.Left -> Ior.Both(a.leftValue + b.value, a.rightValue)
is Ior.Right -> Ior.Both(a.leftValue, a.rightValue + b.value)
is Ior.Both -> Ior.Both(a.leftValue + b.leftValue, a.rightValue + b.rightValue)
}
}
}
}
}

@extension
@undocumented
interface IorFunctor<L> : Functor<IorPartialOf<L>> {
Expand Down Expand Up @@ -245,3 +275,6 @@ interface IorBicrosswalk : Bicrosswalk<ForIor>, IorBifunctor, IorBifoldable {
}
}
}

operator fun <L, R> Ior<L, R>.component1(): L? = leftOrNull()
operator fun <L, R> Ior<L, R>.component2(): R? = orNull()

0 comments on commit c80db20

Please sign in to comment.