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

Replace Option<T> with ? in ListK (part 1) #196

Merged
merged 16 commits into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
116 changes: 115 additions & 1 deletion arrow-core-data/src/main/kotlin/arrow/core/ListK.kt
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,41 @@ data class ListK<out A>(private val list: List<A>) : ListKOf<A>, List<A> by list
}
}

@Deprecated("Deprecated, use mapNotNull(f: (A) -> B?) instead", ReplaceWith("mapNotNull(f: (A) -> B?)"))
fun <B> filterMap(f: (A) -> Option<B>): ListK<B> =
flatMap { a -> f(a).fold({ empty<B>() }, { just(it) }) }

/**
* Returns a [ListK] containing the transformed values from the original
* [ListK] filtering out any null value.
*
* Example:
* ```kotlin:ank:playground
* import arrow.core.*
*
* //sampleStart
* val evenStrings = listOf(1, 2).k().mapNotNull {
* when (it % 2 == 0) {
* true -> it.toString()
* else -> null
* }
* }
* //sampleEnd
*
* fun main() {
* println("evenStrings = $evenStrings")
* }
* ```
*/
fun <B> mapNotNull(f: (A) -> B?): ListK<B> =
flatMap { a -> f(a)?.let { just(it) } ?: empty<B>() }

override fun hashCode(): Int = list.hashCode()

/**
* Align two Lists as in zip, but filling in blanks with None.
*/
@Deprecated("Deprecated, use `padZipWithNull` instead", ReplaceWith("padZipWithNull(other: ListK<B>)"))
fun <B> padZip(
other: ListK<B>
): ListK<Tuple2<Option<A>, Option<B>>> =
Expand All @@ -185,23 +212,110 @@ data class ListK<out A>(private val list: List<A>) : ListKOf<A>, List<A> by list
{ a, b -> a.some() toT b.some() })
}

/**
* Returns a [ListK<Tuple2<A?, B?>>] containing the zipped values of the two listKs
* with null for padding.
*
* Example:
* ```kotlin:ank:playground
* import arrow.core.*
*
* //sampleStart
* val padRight = listOf(1, 2).k().padZip(listOf("a").k()) // Result: ListK(Tuple2(1, "a"), Tuple2(2, null))
* val padLeft = listOf(1).k().padZip(listOf("a", "b").k()) // Result: ListK(Tuple2(1, "a"), Tuple2(null, "b"))
* val noPadding = listOf(1, 2).k().padZip(listOf("a", "b").k()) // Result: ListK(Tuple2(1, "a"), Tuple2(2, "b"))
* //sampleEnd
*
* fun main() {
* println("padRight = $padRight")
* println("padLeft = $padLeft")
* println("noPadding = $noPadding")
* }
* ```
*/
fun <B> padZipWithNull(
other: ListK<B>
): ListK<Tuple2<A?, B?>> =
alignWith(this, other) { ior ->
ior.fold(
{ it toT null },
{ null toT it },
{ a, b -> a toT b })
}

/**
* Align two Lists as in zipWith, but filling in blanks with None.
*/
@Deprecated("Deprecated, use `padZip(other: ListK<B>, fa: (A?, B?) -> C)` instead", ReplaceWith("padZip(other: ListK<B>, fa: (A?, B?) -> C)"))
fun <B, C> padZipWith(
other: ListK<B>,
fa: (Option<A>, Option<B>) -> C
): ListK<C> =
padZip(other).map { fa(it.a, it.b) }

/**
* Returns a [ListK<C>] containing the result of applying some transformation `(A?, B?) -> C`
* on a zip.
*
* Example:
* ```kotlin:ank:playground
* import arrow.core.*
*
* //sampleStart
* val padZipRight = listOf(1, 2).k().padZip(listOf("a").k()) { l, r -> l toT r }.k() // Result: ListK(Tuple2(1, "a"), Tuple2(2, null))
* val padZipLeft = listOf(1).k().padZip(listOf("a", "b").k()) { l, r -> l toT r }.k() // Result: ListK(Tuple2(1, "a"), Tuple2(null, "b"))
* val noPadding = listOf(1, 2).k().padZip(listOf("a", "b").k()) { l, r -> l toT r }.k() // Result: ListK(Tuple2(1, "a"), Tuple2(2, "b"))
* //sampleEnd
*
* fun main() {
* println("padZipRight = $padZipRight")
* println("padZipLeft = $padZipLeft")
* println("noPadding = $noPadding")
* }
* ```
*/
fun <B, C> padZip(
other: ListK<B>,
fa: (A?, B?) -> C
): ListK<C> =
padZipWithNull(other).map { fa(it.a, it.b) }

/**
* Left-padded zipWith.
*/
@Deprecated("Deprecated, use `leftPadZip(other: ListK<B>, fab: (A?, B) -> C)` instead", ReplaceWith("leftPadZip(other: ListK<B>, fab: (A?, B) -> C)"))
fun <B, C> lpadZipWith(
other: ListK<B>,
fab: (Option<A>, B) -> C
): ListK<C> =
padZipWith(other) { a, b -> b.map { fab(a, it) } }.filterMap(::identity)
padZipWith(other) { a: Option<A>, b -> b.map { fab(a, it) } }.filterMap(::identity)

/**
* Returns a [ListK<C>] containing the result of applying some transformation `(A?, B) -> C`
* on a zip, excluding all cases where the right value is null.
*
* Example:
* ```kotlin:ank:playground
* import arrow.core.*
*
* //sampleStart
* val left = listOf(1, 2).k().leftPadZip(listOf("a").k()) { l, r -> l toT r }.k() // Result: ListK(Tuple2(1, "a"))
* val right = listOf(1).k().leftPadZip(listOf("a", "b").k()) { l, r -> l toT r }.k() // Result: ListK(Tuple2(1, "a"), Tuple2(null, "b"))
* val both = listOf(1, 2).k().leftPadZip(listOf("a", "b").k()) { l, r -> l toT r }.k() // Result: ListK(Tuple2(1, "a"), Tuple2(2, "b"))
* //sampleEnd
*
* fun main() {
* println("left = $left")
* println("right = $right")
* println("both = $both")
* }
* ```
*/
fun <B, C> leftPadZip(
other: ListK<B>,
fab: (A?, B) -> C
): ListK<C> =
padZip(other) { a: A?, b: B? -> b?.let { fab(a, it) } }.mapNotNull(::identity)

/**
* Left-padded zip.
Expand Down
73 changes: 73 additions & 0 deletions arrow-core-data/src/test/kotlin/arrow/core/ListKTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package arrow.core
import arrow.Kind
import arrow.core.extensions.eq
import arrow.core.extensions.hash
import arrow.core.extensions.list.zip.zipWith
import arrow.core.extensions.listk.align.align
import arrow.core.extensions.listk.applicative.applicative
import arrow.core.extensions.listk.crosswalk.crosswalk
Expand All @@ -23,6 +24,8 @@ import arrow.core.extensions.listk.show.show
import arrow.core.extensions.listk.traverse.traverse
import arrow.core.extensions.listk.unalign.unalign
import arrow.core.extensions.listk.unzip.unzip
import arrow.core.extensions.listk.zip.zipWith
import arrow.core.extensions.option.eq.eq
import arrow.core.extensions.show
import arrow.core.test.UnitSpec
import arrow.core.test.generators.genK
Expand Down Expand Up @@ -165,6 +168,20 @@ class ListKTest : UnitSpec() {
}
}

"leftPadZip (with map)" {
forAll(Gen.listK(Gen.int()), Gen.listK(Gen.int())) { a, b ->
val left = a.map { it }.k() + List(max(0, b.count() - a.count())) { null }.k()
val right = b.map { it }.k() + List(max(0, a.count() - b.count())) { null }.k()

val result =
a.leftPadZip(b) { a, b ->
a toT b
}

result == left.zipWith(right) { l, r -> l toT r }.filter { it.b != null }
}
}

"rpadzip" {
forAll(Gen.listK(Gen.int()), Gen.listK(Gen.int())) { a, b ->

Expand All @@ -186,6 +203,62 @@ class ListKTest : UnitSpec() {
result.map { it.a }.equalUnderTheLaw(a, ListK.eq(Int.eq()))
}
}

"padZip" {
forAll(Gen.listK(Gen.int()), Gen.listK(Gen.int())) { a, b ->
val left = a.map { Some(it) }.k() + List(max(0, b.count() - a.count())) { None }.k()
val right = b.map { Some(it) }.k() + List(max(0, a.count() - b.count())) { None }.k()

a.padZip(b) == left.zipWith(right) { l, r -> l toT r }
}
}

"padZipWith" {
forAll(Gen.listK(Gen.int()), Gen.listK(Gen.int())) { a, b ->
val left = a.map { Some(it) }.k() + List(max(0, b.count() - a.count())) { None }.k()
val right = b.map { Some(it) }.k() + List(max(0, a.count() - b.count())) { None }.k()
a.padZipWith(b) { l, r -> Ior.fromOptions(l, r) } == left.zipWith(right) { l, r -> Ior.fromOptions(l, r) }
}
}

"padZip (with map)" {
forAll(Gen.listK(Gen.int()), Gen.listK(Gen.int())) { a, b ->
val left = a.map { it }.k() + List(max(0, b.count() - a.count())) { null }.k()
val right = b.map { it }.k() + List(max(0, a.count() - b.count())) { null }.k()
a.padZip(b) { l, r -> Ior.fromNullables(l, r) } == left.zipWith(right) { l, r -> Ior.fromNullables(l, r) }
}
}

"padZipWithNull" {
forAll(Gen.listK(Gen.int()), Gen.listK(Gen.int())) { a, b ->
val left = a.map { it }.k() + List(max(0, b.count() - a.count())) { null }.k()
val right = b.map { it }.k() + List(max(0, a.count() - b.count())) { null }.k()

a.padZipWithNull(b) == left.zipWith(right) { l, r -> l toT r }
}
}

"filterMap() should map list and filter out None values" {
forAll(Gen.listK(Gen.int())) { listk ->
listk.filterMap {
when (it % 2 == 0) {
true -> it.toString().toOption()
else -> None
}
} == listk.toList().filter { it % 2 == 0 }.map { it.toString() }.k()
}
}

"mapNotNull() should map list and filter out null values" {
forAll(Gen.listK(Gen.int())) { listk ->
listk.mapNotNull {
when (it % 2 == 0) {
true -> it.toString()
else -> null
}
} == listk.toList().filter { it % 2 == 0 }.map { it.toString() }.k()
}
}
}

private fun bijection(from: Kind<ForListK, Tuple2<Tuple2<Int, Int>, Int>>): ListK<Tuple2<Int, Tuple2<Int, Int>>> =
Expand Down