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

Add Semiring type class #1266

Merged
merged 22 commits into from
Feb 1, 2019
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
89c314b
Add Semiring type class
juliankotrba Jan 25, 2019
f129c8a
Merge branch 'master' into feature/semiring-type-class
juliankotrba Jan 26, 2019
fd556f1
Add Semiring laws
juliankotrba Jan 26, 2019
852c395
Add Semiring number instances
juliankotrba Jan 26, 2019
3f87609
Add Semiring number instance tests
juliankotrba Jan 26, 2019
c6fdb82
Add Semiring documentation template
juliankotrba Jan 26, 2019
52244ed
Rename function product to combineMultiplication
juliankotrba Jan 27, 2019
ac998db
Merge branch 'master' into feature/semiring-type-class
juliankotrba Jan 27, 2019
1799356
Merge remote-tracking branch 'origin/feature/semiring-type-class' int…
juliankotrba Jan 27, 2019
a2b7f82
Fix Semiring number instance tests
juliankotrba Jan 27, 2019
134d55e
Add OptionSemiring
juliankotrba Jan 28, 2019
6ea8e37
Merge branch 'master' into feature/semiring-type-class
raulraja Jan 28, 2019
a3ec74a
Add Semiring documentation
juliankotrba Jan 28, 2019
6a00d50
Merge remote-tracking branch 'origin/feature/semiring-type-class' int…
juliankotrba Jan 28, 2019
700c99e
Merge branch 'master' into feature/semiring-type-class
juliankotrba Jan 29, 2019
0b7b3cd
Merge branch 'master' into feature/semiring-type-class
raulraja Jan 29, 2019
7f1c6f3
Update Semiring documentation to new format
juliankotrba Jan 29, 2019
7d5f8a0
Merge remote-tracking branch 'origin/feature/semiring-type-class' int…
juliankotrba Jan 29, 2019
8aece4a
Merge branch 'master' into feature/semiring-type-class
juliankotrba Jan 30, 2019
f756888
Make maybeCombineMultiplicate() receiver nullable
juliankotrba Jan 30, 2019
5b88e7b
Add maybeCombineAddition with nullable receiver
juliankotrba Jan 30, 2019
b16de1b
Merge branch 'master' into feature/semiring-type-class
juliankotrba Jan 31, 2019
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package arrow.extensions

import arrow.core.extensions.semiring
import arrow.test.UnitSpec
import arrow.test.laws.SemiringLaws
import io.kotlintest.runner.junit4.KotlinTestRunner
import org.junit.runner.RunWith

@RunWith(KotlinTestRunner::class)
class NumberSemiringTest : UnitSpec() {

companion object {
private const val A = 2
private const val B = 3
private const val C = 4
}

init {
testLaws(SemiringLaws.laws(Byte.semiring(), A.toByte(), B.toByte(), C.toByte()))
testLaws(SemiringLaws.laws(Double.semiring(), A.toDouble(), B.toDouble(), C.toDouble()))
testLaws(SemiringLaws.laws(Int.semiring(), A, B, C))
testLaws(SemiringLaws.laws(Short.semiring(), A.toShort(), B.toShort(), C.toShort()))
testLaws(SemiringLaws.laws(Float.semiring(), A.toFloat(), B.toFloat(), C.toFloat()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ interface ByteMonoid: Monoid<Byte>, ByteSemigroup {
override fun empty(): Byte = 0
}

interface ByteSemiring: Semiring<Byte> {
override fun zero(): Byte = 0
override fun one(): Byte = 1

override fun Byte.combine(b: Byte): Byte = (this + b).toByte()
override fun Byte.combineMultiplicate(b: Byte): Byte = (this * b).toByte()
}

interface ByteOrder : Order<Byte> {
override fun Byte.compare(b: Byte): Int = compareTo(b)
}
Expand Down Expand Up @@ -47,6 +55,9 @@ fun Byte.Companion.semigroup(): Semigroup<Byte> =
fun Byte.Companion.monoid(): Monoid<Byte> =
object : ByteMonoid{}

fun Byte.Companion.semiring(): Semiring<Byte> =
object : ByteSemiring{}

//////////
// Double
//////////
Expand All @@ -59,6 +70,14 @@ interface DoubleMonoid: Monoid<Double>, DoubleSemigroup {
override fun empty(): Double = .0
}

interface DoubleSemiring: Semiring<Double> {
override fun zero(): Double = .0
override fun one(): Double = 1.0

override fun Double.combine(b: Double): Double = this + b
override fun Double.combineMultiplicate(b: Double): Double = this * b
}

interface DoubleOrder : Order<Double> {
override fun Double.compare(b: Double): Int = compareTo(b)
}
Expand Down Expand Up @@ -93,6 +112,9 @@ fun Double.Companion.semigroup(): Semigroup<Double> =
fun Double.Companion.monoid(): Monoid<Double> =
object : DoubleMonoid{}

fun Double.Companion.semiring(): Semiring<Double> =
object : DoubleSemiring{}

//////////
// Int
//////////
Expand All @@ -104,6 +126,14 @@ interface IntMonoid: Monoid<Int>, IntSemigroup {
override fun empty(): Int = 0
}

interface IntSemiring: Semiring<Int> {
override fun zero(): Int = 0
override fun one(): Int = 1

override fun Int.combine(b: Int): Int = this + b
override fun Int.combineMultiplicate(b: Int): Int = this * b
}

interface IntEq : Eq<Int> {
override fun Int.eqv(b: Int): Boolean = this == b
}
Expand Down Expand Up @@ -138,6 +168,9 @@ fun Int.Companion.semigroup(): Semigroup<Int> =
fun Int.Companion.monoid(): Monoid<Int> =
object : IntMonoid{}

fun Int.Companion.semiring(): Semiring<Int> =
object : IntSemiring{}

//////////
// Long
//////////
Expand All @@ -150,6 +183,14 @@ interface LongMonoid: Monoid<Long>, LongSemigroup {
override fun empty(): Long = 0L
}

interface LongSemiring: Semiring<Long> {
override fun zero(): Long = 0
override fun one(): Long = 1

override fun Long.combine(b: Long): Long = this + b
override fun Long.combineMultiplicate(b: Long): Long = this * b
}

interface LongOrder : Order<Long> {
override fun Long.compare(b: Long): Int = compareTo(b)
}
Expand Down Expand Up @@ -184,6 +225,9 @@ fun Long.Companion.semigroup(): Semigroup<Long> =
fun Long.Companion.monoid(): Monoid<Long> =
object : LongMonoid{}

fun Long.Companion.semiring(): Semiring<Long> =
object : LongSemiring{}

//////////
// Short
//////////
Expand All @@ -196,6 +240,14 @@ interface ShortMonoid: Monoid<Short>, ShortSemigroup {
override fun empty(): Short = 0
}

interface ShortSemiring: Semiring<Short> {
override fun zero(): Short = 0
override fun one(): Short = 1

override fun Short.combine(b: Short): Short = (this + b).toShort()
override fun Short.combineMultiplicate(b: Short): Short = (this * b).toShort()
}

interface ShortOrder : Order<Short> {
override fun Short.compare(b: Short): Int = compareTo(b)
}
Expand Down Expand Up @@ -230,6 +282,9 @@ fun Short.Companion.semigroup(): Semigroup<Short> =
fun Short.Companion.monoid(): Monoid<Short> =
object : ShortMonoid{}

fun Short.Companion.semiring(): Semiring<Short> =
object : ShortSemiring{}

//////////
// Float
//////////
Expand All @@ -242,6 +297,14 @@ interface FloatMonoid: Monoid<Float>, FloatSemigroup {
override fun empty(): Float = 0f
}

interface FloatSemiring: Semiring<Float> {
override fun zero(): Float = 0f
override fun one(): Float = 1f

override fun Float.combine(b: Float): Float = this + b
override fun Float.combineMultiplicate(b: Float): Float = this * b
}

interface FloatOrder : Order<Float> {
override fun Float.compare(b: Float): Int = compareTo(b)
}
Expand Down Expand Up @@ -275,3 +338,6 @@ fun Float.Companion.semigroup(): Semigroup<Float> =

fun Float.Companion.monoid(): Monoid<Float> =
object : FloatMonoid{}

fun Float.Companion.semiring(): Semiring<Float> =
object : FloatSemiring{}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ interface OptionMonoid<A> : Monoid<Option<A>>, OptionSemigroup<A> {
override fun empty(): Option<A> = None
}

@extension
interface OptionSemiring<A> : Semiring<Option<A>> {

fun SG(): Semiring<A>
override fun zero(): Option<A> = None
override fun one(): Option<A> = None

override fun Option<A>.combine(b: Option<A>): Option<A> =
when (this) {
is Some<A> -> when (b) {
is Some<A> -> Some(SG().run { t.combine(b.t) })
None -> this
}
None -> b
}

override fun Option<A>.combineMultiplicate(b: Option<A>): Option<A> =
when (this) {
is Some<A> -> when (b) {
is Some<A> -> Some(SG().run { t.combineMultiplicate(b.t) })
None -> this
}
None -> b
}
}

@extension
interface OptionApplicativeError: ApplicativeError<ForOption, Unit>, OptionApplicative {
override fun <A> raiseError(e: Unit): Option<A> =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package arrow.test.laws

import arrow.typeclasses.Semiring
import io.kotlintest.shouldBe

object SemiringLaws {

fun <F> laws(SG: Semiring<F>, A: F, B: F, C: F): List<Law> =
listOf(
Law("Semiring: Additive commutativity") { SG.semiringAdditiveCommutativity(A, B) },
Law("Semiring: Multiplicative commutativity") { SG.semiringMultiplicativeCommutativity(A, B) },
Law("Semiring: Right distributivity") { SG.semiringRightDistributivity(A, B, C) },
Law("Semiring: Left distributivity") { SG.semiringLeftDistributivity(A, B, C) },
Law("Semiring: Multiplicative left identity") { SG.semiringMultiplicativeLeftIdentity(A, C) },
Law("Semiring: Multiplicative right identity") { SG.semiringMultiplicativeRightIdentity(A) },
Law("Semiring: Multiplicative left absorption") { SG.semiringMultiplicativeLeftAbsorption(A, C) },
Law("Semiring: Multiplicative right absorption") { SG.semiringMultiplicativeRightAbsorption(A, C) }
)

fun <F> Semiring<F>.semiringAdditiveCommutativity(A: F, B: F) =
A.combine(B).shouldBe(B.combine(A))

fun <F> Semiring<F>.semiringMultiplicativeCommutativity(A: F, B: F) =
A.combineMultiplicate(B).shouldBe(B.combineMultiplicate(A))

fun <F> Semiring<F>.semiringRightDistributivity(A: F, B: F, C: F) =
(A.combine(B)).combineMultiplicate(C).shouldBe((A.combineMultiplicate(C)).combine(B.combineMultiplicate(C)))

fun <F> Semiring<F>.semiringLeftDistributivity(A: F, B: F, C: F) =
A.combine(B.combineMultiplicate(C)).shouldBe((A.combineMultiplicate(B)).combine(A.combineMultiplicate(C)))

fun <F> Semiring<F>.semiringMultiplicativeLeftIdentity(A: F, C: F) =
(one().combineMultiplicate(A)).shouldBe(A)

fun <F> Semiring<F>.semiringMultiplicativeRightIdentity(A: F) =
A.combineMultiplicate(one()).shouldBe(A)

fun <F> Semiring<F>.semiringMultiplicativeLeftAbsorption(A: F, C: F) =
(zero().combineMultiplicate(A)).shouldBe(zero())

fun <F> Semiring<F>.semiringMultiplicativeRightAbsorption(A: F, C: F) =
A.combineMultiplicate(zero()).shouldBe(zero())

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package arrow.typeclasses

import arrow.core.Option

/**
* ank_macro_hierarchy(arrow.typeclasses.Semiring)
*/
interface Semiring<A> : Monoid<A> {

/**
* A zero value for this A
*/
fun zero(): A

/**
* A one value for this A
*/
fun one(): A

/**
* Multiplicatively combine two [A] values.
*/
fun A.combineMultiplicate(b: A): A

override fun empty(): A = one()
juliankotrba marked this conversation as resolved.
Show resolved Hide resolved

operator fun A.times(b: A): A =
this.combineMultiplicate(b)

/**
* Maybe multiplicatively combine two [A] values.
*/
fun A.maybeCombineMultiplicate(b: A?): A =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be fun A?.maybeCombineMultiplicate(b: A?): A = instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but I wanted to be consistent with Semigroup's maybeCombine

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I change it in this this PR for both functions, create a new PR or just leave it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd fix both on this PR tbh :P

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

K 😁 But just to be sure, returning zero() if the receiver is null is the expected behavior, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zero for addition, one for multiplication

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mhh maybe the reason why maybeCombine's receiver is not nullable is because maybeCombine is defined in Semigroup, which doesn't have an empty element. To get around this I have added A?.maybeCombineAddition to Semiring

Option.fromNullable(b).fold({ this }, { combineMultiplicate(it) })
}
3 changes: 3 additions & 0 deletions modules/docs/arrow-docs/docs/_data/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ options:
- title: Monoid
url: /docs/arrow/typeclasses/monoid/

- title: Semiring
url: /docs/arrow/typeclasses/semiring/

- title: Foldable
url: /docs/arrow/typeclasses/foldable/

Expand Down
Loading