diff --git a/helios-core/build.gradle b/helios-core/build.gradle index bf117ef..a38cef0 100644 --- a/helios-core/build.gradle +++ b/helios-core/build.gradle @@ -13,6 +13,7 @@ dependencies { testImplementation("io.kotest:kotest-runner-junit5:$kotlinTestVersion") testImplementation("io.kotest:kotest-assertions-arrow:$kotlinTestVersion") + testImplementation("io.arrow-kt:arrow-test:$arrowVersion") kaptTest project(":helios-meta") kaptTest "io.arrow-kt:arrow-meta:$arrowVersion" diff --git a/helios-core/src/main/kotlin/helios/core/helios.kt b/helios-core/src/main/kotlin/helios/core/helios.kt index 07f7d22..5f24a2f 100644 --- a/helios-core/src/main/kotlin/helios/core/helios.kt +++ b/helios-core/src/main/kotlin/helios/core/helios.kt @@ -1,7 +1,9 @@ package helios.core import arrow.core.* +import arrow.core.extensions.monoid import arrow.core.extensions.option.applicative.applicative +import arrow.typeclasses.Monoid import helios.instances.HeliosFacade import helios.instances.jsarray.eq.eq import helios.instances.jsnumber.eq.eq @@ -76,9 +78,6 @@ sealed class Json { is JsBoolean -> ifJsBoolean(this) } - fun add(key: String, value: Json): JsObject = - JsObject(hashMapOf(key to value)) - fun asJsString(): Option = (this as? JsString)?.some() ?: none() @@ -97,12 +96,7 @@ sealed class Json { fun asJsNull(): Option = (this as? JsNull)?.some() ?: none() - fun merge(that: Json): Json = - Option.applicative().map(asJsObject(), that.asJsObject()) { (lhs, rhs) -> - lhs.toList().fold(rhs) { acc, (key, value) -> - rhs[key].fold({ acc.add(key, value) }, { r -> acc.add(key, value.merge(r)) }) - } - }.fix().getOrElse { that } + fun merge(that: Json, M: Monoid): Json = M.combineAll(listOf(this, that)) abstract fun noSpaces(): String @@ -357,7 +351,9 @@ data class JsObject(val value: Map) : Json() { JsObject(keyValues.map { it.a to it.b }.toMap()) } - fun toList(): List> = value.toList().map { it.first toT it.second } + fun add(key: String, value: Json): Json = JsObject(this.value + (key to value)) + + fun toList(): List> = value.toList().map { it.toTuple2() } override fun noSpaces(): String = value.map { (k, v) -> """"$k":${v.noSpaces()}""" }.joinToString( diff --git a/helios-core/src/main/kotlin/helios/instances/instancesEq.kt b/helios-core/src/main/kotlin/helios/instances/instancesEq.kt index 0c407e5..a030e41 100644 --- a/helios-core/src/main/kotlin/helios/instances/instancesEq.kt +++ b/helios-core/src/main/kotlin/helios/instances/instancesEq.kt @@ -10,6 +10,13 @@ import helios.instances.jsnumber.eq.eq import helios.instances.jsobject.eq.eq import helios.instances.json.eq.eq +@extension +interface JsStringEq : Eq { + override fun JsString.eqv(b: JsString): Boolean = with(String.eq()) { + value.toString().eqv(b.value.toString()) + } +} + @extension interface JsObjectEq : Eq { override fun JsObject.eqv(b: JsObject): Boolean = with(Json.eq()) { diff --git a/helios-core/src/main/kotlin/helios/instances/instancesMonoid.kt b/helios-core/src/main/kotlin/helios/instances/instancesMonoid.kt new file mode 100644 index 0000000..5abd621 --- /dev/null +++ b/helios-core/src/main/kotlin/helios/instances/instancesMonoid.kt @@ -0,0 +1,46 @@ +package helios.instances + +import arrow.extension +import arrow.typeclasses.Monoid +import helios.core.JsArray +import helios.core.JsDouble +import helios.core.JsFloat +import helios.core.JsInt +import helios.core.JsLong +import helios.core.JsObject +import helios.core.JsString + +@extension +interface JsIntMonoid : Monoid, JsIntSemigroup { + override fun empty(): JsInt = JsInt(0) +} + +@extension +interface JsLongMonoid : Monoid, JsLongSemigroup { + override fun empty(): JsLong = JsLong(0L) +} + +@extension +interface JsFloatMonoid : Monoid, JsFloatSemigroup { + override fun empty(): JsFloat = JsFloat(0f) +} + +@extension +interface JsDoubleMonoid : Monoid, JsDoubleSemigroup { + override fun empty(): JsDouble = JsDouble(0.0) +} + +@extension +interface JsStringMonoid : Monoid, JsStringSemigroup { + override fun empty(): JsString = JsString("") +} + +@extension +interface JsArrayMonoid : Monoid, JsArraySemigroup { + override fun empty(): JsArray = JsArray(emptyList()) +} + +@extension +interface JsObjectMonoid : Monoid, JsObjectSemigroup { + override fun empty(): JsObject = JsObject(emptyMap()) +} diff --git a/helios-core/src/main/kotlin/helios/instances/instancesSemigroup.kt b/helios-core/src/main/kotlin/helios/instances/instancesSemigroup.kt new file mode 100644 index 0000000..d8e0734 --- /dev/null +++ b/helios-core/src/main/kotlin/helios/instances/instancesSemigroup.kt @@ -0,0 +1,63 @@ +package helios.instances + +import arrow.core.ListK +import arrow.core.extensions.listk.semigroup.semigroup +import arrow.core.extensions.semigroup +import arrow.core.k +import arrow.extension +import arrow.typeclasses.Semigroup +import helios.core.JsArray +import helios.core.JsDouble +import helios.core.JsFloat +import helios.core.JsInt +import helios.core.JsLong +import helios.core.JsObject +import helios.core.JsString +import helios.core.Json + +@extension +interface JsIntSemigroup : Semigroup { + override fun JsInt.combine(b: JsInt): JsInt = JsInt(Int.semigroup().run { + value.combine(b.value) + }) +} + +@extension +interface JsLongSemigroup : Semigroup { + override fun JsLong.combine(b: JsLong): JsLong = JsLong(Long.semigroup().run { + value.combine(b.value) + }) +} + +@extension +interface JsFloatSemigroup : Semigroup { + override fun JsFloat.combine(b: JsFloat): JsFloat = JsFloat(Float.semigroup().run { + value.combine(b.value) + }) +} + +@extension +interface JsDoubleSemigroup : Semigroup { + override fun JsDouble.combine(b: JsDouble): JsDouble = JsDouble(Double.semigroup().run { + value.combine(b.value) + }) +} + +@extension +interface JsStringSemigroup : Semigroup { + override fun JsString.combine(b: JsString): JsString = JsString(String.semigroup().run { + value.toString().combine(b.value.toString()) + }) +} + +@extension +interface JsArraySemigroup : Semigroup { + override fun JsArray.combine(b: JsArray): JsArray = JsArray(ListK.semigroup().run { + value.k().combine(b.value.k()) + }) +} + +@extension +interface JsObjectSemigroup : Semigroup { + override fun JsObject.combine(b: JsObject): JsObject = JsObject(value + b.value) +} diff --git a/helios-core/src/test/kotlin/helios/core/MonoidTest.kt b/helios-core/src/test/kotlin/helios/core/MonoidTest.kt new file mode 100644 index 0000000..f6948ab --- /dev/null +++ b/helios-core/src/test/kotlin/helios/core/MonoidTest.kt @@ -0,0 +1,32 @@ +package helios.core + +import arrow.test.UnitSpec +import arrow.test.laws.MonoidLaws +import arrow.test.laws.SemigroupLaws +import helios.instances.jsstring.eq.eq +import helios.instances.jsstring.monoid.monoid +import helios.instances.jsstring.semigroup.semigroup +import io.kotlintest.properties.Gen + +class MonoidTest : UnitSpec() { + + init { + testLaws( + SemigroupLaws.laws( + JsString.semigroup(), + JsString("a"), + JsString("b"), + JsString("c"), + JsString.eq() + ) + ) + + testLaws( + MonoidLaws.laws( + JsString.monoid(), + Gen.string().map { JsString(it) }, + JsString.eq() + ) + ) + } +} diff --git a/helios-optics/src/test/kotlin/helios/optics/instances.kt b/helios-optics/src/test/kotlin/helios/optics/instances.kt index a9ec8af..cf0f6da 100644 --- a/helios-optics/src/test/kotlin/helios/optics/instances.kt +++ b/helios-optics/src/test/kotlin/helios/optics/instances.kt @@ -1,24 +1,14 @@ package helios.optics -import arrow.core.None -import arrow.core.Option -import arrow.core.extensions.option.eq.eq +import arrow.typeclasses.Eq import helios.arrow.UnitSpec import helios.arrow.generators.functionAToB -import helios.arrow.generators.option -import helios.arrow.laws.LensLaws import helios.arrow.laws.OptionalLaws import helios.arrow.laws.TraversalLaws -import arrow.typeclasses.Eq -import arrow.typeclasses.Monoid import helios.core.JsArray import helios.core.JsObject -import helios.core.Json -import helios.instances.jsobject.eq.eq -import helios.instances.json.eq.eq import helios.optics.jsarray.each.each import helios.optics.jsarray.index.index -import helios.optics.jsobject.at.at import helios.optics.jsobject.each.each import helios.optics.jsobject.index.index import helios.test.generators.alphaStr @@ -29,66 +19,52 @@ import io.kotest.properties.Gen class InstancesTest : UnitSpec() { - init { - - testLaws( - OptionalLaws.laws( - optionalGen = Gen.alphaStr().map { JsObject.index().index(it) }, - aGen = Gen.jsObject(), - bGen = Gen.json(), - funcGen = Gen.functionAToB(Gen.json()), - EQA = Eq.any(), - EQOptionB = Eq.any() - ) - ) - - testLaws( - OptionalLaws.laws( - optional = JsArray.index().index(1), - aGen = Gen.jsArray(), - bGen = Gen.json(), - funcGen = Gen.functionAToB(Gen.json()), - EQA = Eq.any(), - EQOptionB = Eq.any() - ) - ) + init { - testLaws( - TraversalLaws.laws( - traversal = JsObject.each().each(), - aGen = Gen.jsObject(), - bGen = Gen.json(), - funcGen = Gen.functionAToB(Gen.json()), - EQA = Eq.any(), - EQOptionB = Eq.any(), - EQListB = Eq.any() - ) - ) + testLaws( + OptionalLaws.laws( + optionalGen = Gen.alphaStr().map { JsObject.index().index(it) }, + aGen = Gen.jsObject(), + bGen = Gen.json(), + funcGen = Gen.functionAToB(Gen.json()), + EQA = Eq.any(), + EQOptionB = Eq.any() + ) + ) - testLaws( - TraversalLaws.laws( - traversal = JsArray.each().each(), - aGen = Gen.jsArray(), - bGen = Gen.json(), - funcGen = Gen.functionAToB(Gen.json()), - EQA = Eq.any(), - EQOptionB = Eq.any(), - EQListB = Eq.any() - ) - ) + testLaws( + OptionalLaws.laws( + optional = JsArray.index().index(1), + aGen = Gen.jsArray(), + bGen = Gen.json(), + funcGen = Gen.functionAToB(Gen.json()), + EQA = Eq.any(), + EQOptionB = Eq.any() + ) + ) - testLaws(LensLaws.laws( - lensGen = Gen.alphaStr().map { JsObject.at().at(it) }, - aGen = Gen.jsObject(), - bGen = Gen.option(Gen.json()), - funcGen = Gen.functionAToB(Gen.option(Gen.json())), - EQA = JsObject.eq(), - EQB = Option.eq(Json.eq()), - MB = object : Monoid> { - override fun Option.combine(b: Option) = flatMap { a -> b.map { a.merge(it) } } - override fun empty(): Option = None - } - )) + testLaws( + TraversalLaws.laws( + traversal = JsObject.each().each(), + aGen = Gen.jsObject(), + bGen = Gen.json(), + funcGen = Gen.functionAToB(Gen.json()), + EQA = Eq.any(), + EQOptionB = Eq.any(), + EQListB = Eq.any() + ) + ) - } + testLaws( + TraversalLaws.laws( + traversal = JsArray.each().each(), + aGen = Gen.jsArray(), + bGen = Gen.json(), + funcGen = Gen.functionAToB(Gen.json()), + EQA = Eq.any(), + EQOptionB = Eq.any(), + EQListB = Eq.any() + ) + ) + } } \ No newline at end of file