Skip to content

Commit

Permalink
[REFACTOR]: Add laws and tests for Applicative
Browse files Browse the repository at this point in the history
  • Loading branch information
csgn committed Aug 5, 2024
1 parent 9857722 commit a137169
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 19 deletions.
3 changes: 0 additions & 3 deletions src/main/scala/kuram/applicative/Applicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ package applicative

import apply.Apply

/** Applicative
* TODO: description
*/
trait Applicative[F[_]] extends Apply[F] {

/** Convert any value into Applicative.
Expand Down
14 changes: 0 additions & 14 deletions src/main/scala/kuram/applicative/instances.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,6 @@ package object instances {
}
}

object map {
given mapApplicative[K]: Applicative[[V] =>> Map[K, V]] with {
def pure[A](a: => A): Map[K, A] = Map.empty[K, A].withDefaultValue(a)

extension [A](fa: Map[K, A]) {
def ap[B](ff: Map[K, A => B]): Map[K, B] = for {
(k, f) <- ff
a <- fa.get(k)
} yield (k, f(a))
}
}
}

object option {
given optionApplicative: Applicative[Option] with {
def pure[A](a: => A): Option[A] = Option(a)
Expand All @@ -64,7 +51,6 @@ package object instances {

object all {
export list.given
export map.given
export option.given
}
}
4 changes: 2 additions & 2 deletions src/main/scala/kuram/applicative/laws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ package object laws {
fa.map2(F.pure(()))((a, _) => a) == fa

/** must obey `x.product(y).product(z) == x.product(y.product(z)).map(assoc)` */
def associativity[A, B, C, Z](fa: F[A], fb: F[B], fc: F[C], f: (A, B, C) => Z): Boolean =
def associativity[A, B, C, Z](fa: F[A], fb: F[B], fc: F[C]): Boolean =
fa.product(fb).product(fc) == fa.product(fb.product(fc)).map { case (a, (b, c)) =>
f(a, b, c)
((a, b), c)
}

/** must obey `x.map2(y)((a, b) => ((f(a), g(b)))) == x.map(f).product(y.map(g))` */
Expand Down
131 changes: 131 additions & 0 deletions tests/src/test/scala/kuram/ApplicativeSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (c) 2024 kattulib
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package kuram

import applicative.Applicative
import applicative.instances.all.given
import applicative.laws.{ApplicativeLaws => laws}

class ApplicativeSuite extends munit.FunSuite {
val listInputs = List(
List(),
List(1),
List(1, 2, 3),
List('a', 'b', 'c'),
List("hello", "world"),
List(List(), List()),
List(List(1), List(1)),
List(List(List(1)), List(1))
)

val optionInputs = List(
Option(1),
Option(None),
Option((1, 2, 3)),
Option(List('a', 'b', 'c')),
Option(Array("hello", "world")),
Option(List(List(), List())),
Option(List(List(1), List(1))),
Option(List(List(List(1)), List((1, 2, 3))))
)

test("Should satisfy identity law") {
listInputs.foreach(fa => {
val obtained = laws(using Applicative[List]).identity(fa)
assertEquals(obtained, true)
})

optionInputs.foreach(fa => {
val obtained = laws(using Applicative[Option]).identity(fa)
assertEquals(obtained, true)
})
}

test("Should satisfy homomorphism law") {
val f = (x: Int) => x + 1
val x = 1

val listObtained = laws(using Applicative[List]).homomorphism(x, f)
assertEquals(listObtained, true)

val optionObtained = laws(using Applicative[Option]).homomorphism(x, f)
assertEquals(optionObtained, true)
}

test("Should satisfy left identity law") {
listInputs.foreach(fa => {
val obtained = laws(using Applicative[List]).leftIdentity(fa)
assertEquals(obtained, true)
})

optionInputs.foreach(fa => {
val obtained = laws(using Applicative[Option]).leftIdentity(fa)
assertEquals(obtained, true)
})
}

test("Should satisfy right identity law") {
listInputs.foreach(fa => {
val obtained = laws(using Applicative[List]).rightIdentity(fa)
assertEquals(obtained, true)
})

optionInputs.foreach(fa => {
val obtained = laws(using Applicative[Option]).rightIdentity(fa)
assertEquals(obtained, true)
})
}

test("Should satisfy associativity law") {
List(
(List(), List(), List()),
(List(1), List(2), List(3)),
(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)),
(List('a'), List('b', 'c'), List('e'))
).foreach((fa, fb, fc) => {
val obtained = laws(using Applicative[List]).associativity(fa, fb, fc)
assertEquals(obtained, true)
})
}

test("Should satisfy naturality law") {
val f = (x: Int) => x + 1
val g = (x: Int) => x - 1

List(
(List(1), List(2))
).foreach((fa, fb) => {
val obtained = laws(using Applicative[List]).naturality(fa, fb, f, g)
assertEquals(obtained, true)
})

List(
(None, None),
(Option(1), Option(2)),
(None, Option(2)),
(Option(1), None)
).foreach((fa, fb) => {
val obtained = laws(using Applicative[Option]).naturality(fa, fb, f, g)
assertEquals(obtained, true)
})
}
}

0 comments on commit a137169

Please sign in to comment.