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

Commit

Permalink
Replace Option<T> with ? in ListK (part 1) (#196)
Browse files Browse the repository at this point in the history
* add tests for filterMap and filterNullMap

* add docs for filterNullMap

* passing test for padZip

* add `padZipWithNull`

* test for `padZipWih`

* rename nullable `padZipWith` to `padZipWithNull` so it won't break existing type inference

* update docs to have expected results

* rename conflicting fn names and fix docs

* leftPadZip with map

* revert weird spacing added to `settings.gradle`

* rename `filterNullMap` -> `mapNotNull`

* Update arrow-core-data/src/main/kotlin/arrow/core/ListK.kt

Co-authored-by: Alberto Ballano <aballano@users.noreply.github.com>

* Update arrow-core-data/src/main/kotlin/arrow/core/ListK.kt

Co-authored-by: Alberto Ballano <aballano@users.noreply.github.com>

* Update arrow-core-data/src/main/kotlin/arrow/core/ListK.kt

Co-authored-by: Alberto Ballano <aballano@users.noreply.github.com>

Co-authored-by: Tavish Pegram <tapegram@gmail.com>
Co-authored-by: Alberto Ballano <aballano@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 12, 2020
1 parent c80db20 commit 6eff086
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 1 deletion.
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

0 comments on commit 6eff086

Please sign in to comment.