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

Improve Nel constructor #2728

Merged
merged 10 commits into from May 27, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,22 +1,17 @@
package arrow.core.test

import arrow.core.NonEmptyList
import arrow.core.Tuple4
import arrow.core.Tuple5
import arrow.core.*
import arrow.core.test.generators.unit
import arrow.core.test.laws.Law
import io.kotest.core.names.TestName
import io.kotest.core.source.SourceRef
import io.kotest.core.spec.RootTest
import io.kotest.core.spec.style.StringSpec
import io.kotest.core.spec.style.scopes.StringSpecScope
import io.kotest.core.spec.style.scopes.addTest
import io.kotest.core.test.TestType
import io.kotest.property.Arb
import io.kotest.property.Gen
import io.kotest.property.PropertyContext
import io.kotest.property.arbitrary.bind
import io.kotest.property.arbitrary.filter
import io.kotest.property.arbitrary.filterIsInstance
import io.kotest.property.arbitrary.map
import io.kotest.property.arbitrary.list as KList
import io.kotest.property.arbitrary.map as KMap
Expand All @@ -38,8 +33,12 @@ public abstract class UnitSpec(
public fun <A> Arb.Companion.list(gen: Gen<A>, range: IntRange = 0..maxDepth): Arb<List<A>> =
Arb.KList(gen, range)

public fun <A> Arb.Companion.nonEmptyList(arb: Arb<A>, depth: Int = maxDepth): Arb<NonEmptyList<A>> =
Arb.list(arb, 1..max(1, depth)).filter(List<A>::isNotEmpty).map(NonEmptyList.Companion::fromListUnsafe)
public fun <A> Arb.Companion.nonEmptyList(arb: Arb<A>, depth: Int = maxDepth): Arb<NonEmptyList<A>> {
return Arb.list(arb, 1..max(1, depth))
.map { Option.fromNullable(it.toNonEmptyListOrNull()) }
.filterIsInstance<Option<NonEmptyList<A>>, Some<NonEmptyList<A>>>()
.map { it.value }
}

public fun <A> Arb.Companion.sequence(arbA: Arb<A>, range: IntRange = 0..maxDepth): Arb<Sequence<A>> =
Arb.list(arbA, range).map { it.asSequence() }
Expand Down
1 change: 1 addition & 0 deletions arrow-libs/core/arrow-core/api/arrow-core.api
Expand Up @@ -751,6 +751,7 @@ public final class arrow/core/NonEmptyListKt {
public static final fun sequenceEither (Larrow/core/NonEmptyList;)Larrow/core/Either;
public static final fun sequenceOption (Larrow/core/NonEmptyList;)Larrow/core/Option;
public static final fun sequenceValidated (Larrow/core/NonEmptyList;Larrow/typeclasses/Semigroup;)Larrow/core/Validated;
public static final fun toNonEmptyListOrNull (Ljava/lang/Iterable;)Larrow/core/NonEmptyList;
public static final fun traverse (Larrow/core/NonEmptyList;Larrow/typeclasses/Semigroup;Lkotlin/jvm/functions/Function1;)Larrow/core/Validated;
public static final fun traverse (Larrow/core/NonEmptyList;Lkotlin/jvm/functions/Function1;)Larrow/core/Either;
public static final fun traverse (Larrow/core/NonEmptyList;Lkotlin/jvm/functions/Function1;)Larrow/core/Option;
Expand Down
Expand Up @@ -10,25 +10,27 @@ public typealias Nel<A> = NonEmptyList<A>

/**
* `NonEmptyList` is a data type used in __Λrrow__ to model ordered lists that guarantee to have at least one value.
* `NonEmptyList` is available in the `arrow-core` module under the `import arrow.core.NonEmptyList`
*
* ## nonEmptyListOf
* ## Constructing NonEmptyList
*
* A `NonEmptyList` guarantees the list always has at least 1 element.
*
* ```kotlin
* import arrow.core.nonEmptyListOf
* import arrow.core.toNonEmptyListOrNull()
*
* val value =
* //sampleStart
* // nonEmptyListOf() // does not compile
* nonEmptyListOf(1, 2, 3, 4, 5) // NonEmptyList<Int>
* //sampleEnd
* fun main() {
* println(value)
* println(nonEmptyListOf(1, 2, 3, 4, 5))
* println(listOf(1, 2, 3).toNonEmptyListOrNull())
* println(emptyList<Int>().toNonEmptyListOrNull())
* }
* ```
* <!--- KNIT example-nonemptylist-01.kt -->
* ```text
* NonEmptyList(1, 2, 3, 4, 5)
* NonEmptyList(1, 2, 3)
* null
* ```
*
* ## head
*
Expand Down Expand Up @@ -232,37 +234,33 @@ public class NonEmptyList<out A>(

public companion object {

@Deprecated(
"Use toNonEmptyListOrNull instead",
ReplaceWith(
"Option.fromNullable<NonEmptyList<A>>(l.toNonEmptyListOrNull())",
"import arrow.core.toNonEmptyListOrNull",
"import arrow.core.Option",
"import arrow.core.NonEmptyList"
)
)
@JvmStatic
public fun <A> fromList(l: List<A>): Option<NonEmptyList<A>> =
if (l.isEmpty()) None else Some(NonEmptyList(l))

@Deprecated(
"Use toNonEmptyListOrNull instead",
ReplaceWith(
"l.toNonEmptyListOrNull() ?: throw IndexOutOfBoundsException(\"Empty list doesn't contain element at index 0.\")",
"import arrow.core.toNonEmptyListOrNull"
)
)
@JvmStatic
public fun <A> fromListUnsafe(l: List<A>): NonEmptyList<A> =
NonEmptyList(l)

@PublishedApi
internal val unit: NonEmptyList<Unit> =
nonEmptyListOf(Unit)

@Suppress("UNCHECKED_CAST")
private tailrec fun <A, B> go(
buf: ArrayList<B>,
f: (A) -> NonEmptyList<Either<A, B>>,
v: NonEmptyList<Either<A, B>>
) {
val head: Either<A, B> = v.head
when (head) {
is Either.Right -> {
buf += head.value
val x = fromList(v.tail)
when (x) {
is Some<NonEmptyList<Either<A, B>>> -> go(buf, f, x.value)
is None -> Unit
}
}
is Either.Left -> go(buf, f, f(head.value) + v.tail)
}
}
}

public fun <B> zip(fb: NonEmptyList<B>): NonEmptyList<Pair<A, B>> =
Expand Down Expand Up @@ -427,7 +425,8 @@ public inline fun <E, A, B> NonEmptyList<A>.traverse(f: (A) -> Either<E, B>): Ei
}
}
// Safe due to traverse laws
return NonEmptyList.fromListUnsafe(acc).right()
return (acc.toNonEmptyListOrNull()
?: throw IndexOutOfBoundsException("Empty list doesn't contain element at index 0.")).right()
i-walker marked this conversation as resolved.
Show resolved Hide resolved
}

@Deprecated("sequenceEither is being renamed to sequence to simplify the Arrow API", ReplaceWith("sequence()", "arrow.core.sequence"))
Expand Down Expand Up @@ -464,7 +463,9 @@ public inline fun <E, A, B> NonEmptyList<A>.traverse(
is Invalid -> semigroup.run { Invalid(acc.value.combine(res.value)) }
}
}
}.map { NonEmptyList.fromListUnsafe(it) }
}.map {
it.toNonEmptyListOrNull() ?: throw IndexOutOfBoundsException("Empty list doesn't contain element at index 0.")
}

@Deprecated("sequenceValidated is being renamed to sequence to simplify the Arrow API", ReplaceWith("sequence()", "arrow.core.sequence"))
public fun <E, A> NonEmptyList<Validated<E, A>>.sequenceValidated(semigroup: Semigroup<E>): Validated<E, NonEmptyList<A>> =
Expand All @@ -491,7 +492,8 @@ public inline fun <A, B> NonEmptyList<A>.traverse(f: (A) -> Option<B>): Option<N
}
}
// Safe due to traverse laws
return NonEmptyList.fromListUnsafe(acc).some()
return (acc.toNonEmptyListOrNull()
?: throw IndexOutOfBoundsException("Empty list doesn't contain element at index 0.")).some()
}

@Deprecated("sequenceOption is being renamed to sequence to simplify the Arrow API", ReplaceWith("sequence()", "arrow.core.sequence"))
Expand All @@ -500,3 +502,6 @@ public fun <A> NonEmptyList<Option<A>>.sequenceOption(): Option<NonEmptyList<A>>

public fun <A> NonEmptyList<Option<A>>.sequence(): Option<NonEmptyList<A>> =
traverse(::identity)

public fun <A> Iterable<A>.toNonEmptyListOrNull(): NonEmptyList<A>? =
firstOrNull()?.let { NonEmptyList(it, drop(1)) }
Expand Up @@ -197,8 +197,8 @@ class IterableTest : UnitSpec() {
ints.map { i -> if (i % 2 == 0) Valid(i) else Invalid(nonEmptyListOf(i)) }
.sequence()

val expected: ValidatedNel<Int, List<Int>> = NonEmptyList.fromList(ints.filterNot { it % 2 == 0 })
.fold({ Valid(ints.filter { it % 2 == 0 }) }, { Invalid(it) })
val expected: ValidatedNel<Int, List<Int>> = ints.filterNot { it % 2 == 0 }
.toNonEmptyListOrNull()?.invalid() ?: Valid(ints.filter { it % 2 == 0 })

res shouldBe expected
}
Expand Down
Expand Up @@ -98,7 +98,7 @@ class MapKTest : UnitSpec() {
ints.traverse(Semigroup.nonEmptyList()) { i -> if (i % 2 == 0) i.validNel() else i.invalidNel() }

val expected: ValidatedNel<Int, Map<Int, Int>> =
NonEmptyList.fromList(ints.values.filterNot { it % 2 == 0 })
Option.fromNullable(ints.values.filterNot { it % 2 == 0 }.toNonEmptyListOrNull())
.fold(
{ ints.entries.filter { (_, v) -> v % 2 == 0 }.map { (k, v) -> k to v }.toMap().validNel() },
{ it.invalid() })
Expand Down
Expand Up @@ -18,12 +18,12 @@ class NonEmptyListTest : UnitSpec() {
"traverse for Either stack-safe" {
// also verifies result order and execution order (l to r)
val acc = mutableListOf<Int>()
val res = NonEmptyList.fromListUnsafe((0..20_000).toList()).traverse { a ->
val res = (0..20_000).toNonEmptyListOrNull()?.traverse { a ->
acc.add(a)
Either.Right(a)
}
res shouldBe Either.Right(NonEmptyList.fromListUnsafe(acc))
res shouldBe Either.Right(NonEmptyList.fromListUnsafe((0..20_000).toList()))
res shouldBe Either.Right(acc.toNonEmptyListOrNull())
res shouldBe Either.Right((0..20_000).toNonEmptyListOrNull())
}

"traverse for Either short-circuit" {
Expand Down Expand Up @@ -53,12 +53,12 @@ class NonEmptyListTest : UnitSpec() {
"traverse for Option is stack-safe" {
// also verifies result order and execution order (l to r)
val acc = mutableListOf<Int>()
val res = NonEmptyList.fromListUnsafe((0..20_000).toList()).traverse { a ->
val res = (0..20_000).toNonEmptyListOrNull()?.traverse { a ->
acc.add(a)
Some(a)
}
res shouldBe Some(NonEmptyList.fromListUnsafe(acc))
res shouldBe Some(NonEmptyList.fromListUnsafe((0..20_000).toList()))
res shouldBe Some(acc.toNonEmptyListOrNull())
res shouldBe Some((0..20_000).toNonEmptyListOrNull())
}

"traverse for Option short-circuits" {
Expand Down Expand Up @@ -105,8 +105,8 @@ class NonEmptyListTest : UnitSpec() {
val res: ValidatedNel<Int, NonEmptyList<Int>> =
ints.traverse(Semigroup.nonEmptyList()) { i: Int -> if (i % 2 == 0) i.validNel() else i.invalidNel() }

val expected: ValidatedNel<Int, NonEmptyList<Int>> = NonEmptyList.fromList(ints.filterNot { it % 2 == 0 })
.fold({ NonEmptyList.fromListUnsafe(ints.filter { it % 2 == 0 }).validNel() }, { it.invalid() })
val expected: ValidatedNel<Int, NonEmptyList<Int>> =
ints.filterNot { it % 2 == 0 }.toNonEmptyListOrNull()?.invalid() ?: ints.filter { it % 2 == 0 }.toNonEmptyListOrNull()!!.valid()

res shouldBe expected
}
Expand Down Expand Up @@ -134,7 +134,7 @@ class NonEmptyListTest : UnitSpec() {
"zip2" {
checkAll(Arb.nonEmptyList(Arb.int()), Arb.nonEmptyList(Arb.int())) { a, b ->
val result = a.zip(b)
val expected = a.all.zip(b.all).let(NonEmptyList.Companion::fromListUnsafe)
val expected = a.all.zip(b.all).toNonEmptyListOrNull()
result shouldBe expected
}
}
Expand All @@ -146,7 +146,7 @@ class NonEmptyListTest : UnitSpec() {
Arb.nonEmptyList(Arb.int())
) { a, b, c ->
val result = a.zip(b, c, ::Triple)
val expected = a.all.zip(b.all, c.all, ::Triple).let(NonEmptyList.Companion::fromListUnsafe)
val expected = a.all.zip(b.all, c.all, ::Triple).toNonEmptyListOrNull()
result shouldBe expected
}
}
Expand All @@ -159,7 +159,7 @@ class NonEmptyListTest : UnitSpec() {
Arb.nonEmptyList(Arb.int())
) { a, b, c, d ->
val result = a.zip(b, c, d, ::Tuple4)
val expected = a.all.zip(b.all, c.all, d.all, ::Tuple4).let(NonEmptyList.Companion::fromListUnsafe)
val expected = a.all.zip(b.all, c.all, d.all, ::Tuple4).toNonEmptyListOrNull()
result shouldBe expected
}
}
Expand All @@ -173,7 +173,7 @@ class NonEmptyListTest : UnitSpec() {
Arb.nonEmptyList(Arb.int())
) { a, b, c, d, e ->
val result = a.zip(b, c, d, e, ::Tuple5)
val expected = a.all.zip(b.all, c.all, d.all, e.all, ::Tuple5).let(NonEmptyList.Companion::fromListUnsafe)
val expected = a.all.zip(b.all, c.all, d.all, e.all, ::Tuple5).toNonEmptyListOrNull()
result shouldBe expected
}
}
Expand All @@ -189,7 +189,7 @@ class NonEmptyListTest : UnitSpec() {
) { a, b, c, d, e, f ->
val result = a.zip(b, c, d, e, f, ::Tuple6)
val expected =
a.all.zip(b.all, c.all, d.all, e.all, f.all, ::Tuple6).let(NonEmptyList.Companion::fromListUnsafe)
a.all.zip(b.all, c.all, d.all, e.all, f.all, ::Tuple6).toNonEmptyListOrNull()
result shouldBe expected
}
}
Expand All @@ -206,7 +206,7 @@ class NonEmptyListTest : UnitSpec() {
) { a, b, c, d, e, f, g ->
val result = a.zip(b, c, d, e, f, g, ::Tuple7)
val expected =
a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, ::Tuple7).let(NonEmptyList.Companion::fromListUnsafe)
a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, ::Tuple7).toNonEmptyListOrNull()
result shouldBe expected
}
}
Expand All @@ -224,7 +224,7 @@ class NonEmptyListTest : UnitSpec() {
) { a, b, c, d, e, f, g, h ->
val result = a.zip(b, c, d, e, f, g, h, ::Tuple8)
val expected = a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, h.all, ::Tuple8)
.let(NonEmptyList.Companion::fromListUnsafe)
.toNonEmptyListOrNull()
result shouldBe expected
}
}
Expand All @@ -243,7 +243,7 @@ class NonEmptyListTest : UnitSpec() {
) { a, b, c, d, e, f, g, h, i ->
val result = a.zip(b, c, d, e, f, g, h, i, ::Tuple9)
val expected = a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, h.all, i.all, ::Tuple9)
.let(NonEmptyList.Companion::fromListUnsafe)
.toNonEmptyListOrNull()
result shouldBe expected
}
}
Expand All @@ -263,7 +263,7 @@ class NonEmptyListTest : UnitSpec() {
) { a, b, c, d, e, f, g, h, i, j ->
val result = a.zip(b, c, d, e, f, g, h, i, j, ::Tuple10)
val expected = a.all.zip(b.all, c.all, d.all, e.all, f.all, g.all, h.all, i.all, j.all, ::Tuple10)
.let(NonEmptyList.Companion::fromListUnsafe)
.toNonEmptyListOrNull()
result shouldBe expected
}
}
Expand Down
Expand Up @@ -62,13 +62,13 @@ class SequenceKTest : UnitSpec() {

"traverse for Validated acummulates" {
checkAll(Arb.sequence(Arb.int())) { ints ->
val res: ValidatedNel<Int, List<Int>> = ints.map { i -> if (i % 2 == 0) i.validNel() else i.invalidNel() }
.sequence(Semigroup.nonEmptyList())
val res: ValidatedNel<Int, List<Int>> = ints.map { i -> if (i % 2 == 0) i.validNel() else i.invalidNel() }
.sequence(Semigroup.nonEmptyList())

val expected: ValidatedNel<Int, Sequence<Int>> = NonEmptyList.fromList(ints.filterNot { it % 2 == 0 }.toList())
.fold({ ints.filter { it % 2 == 0 }.validNel() }, { it.invalid() })
val expected: ValidatedNel<Int, Sequence<Int>> = ints.filterNot { it % 2 == 0 }.toNonEmptyListOrNull()
.fold({ ints.filter { it % 2 == 0 }.validNel() }, { it.invalid() })

res.map { it.toList() } shouldBe expected.map { it.toList() }
res.map { it.toList() } shouldBe expected.map { it.toList() }
}
}

Expand Down
Expand Up @@ -18,5 +18,6 @@ public void testUsage() {
));
Option<NonEmptyList<Integer>> nonEmptyListOption = NonEmptyList.fromList(Arrays.asList(1, 2, 3));
NonEmptyList<Integer> integers1 = NonEmptyList.fromListUnsafe(Arrays.asList(1, 2, 3));
NonEmptyList<Integer> integers2 = NonEmptyListKt.toNonEmptyListOrNull(Arrays.asList(1, 2, 3));
}
}
Expand Up @@ -2,6 +2,7 @@ package arrow.optics.typeclasses

import arrow.core.NonEmptyList
import arrow.core.Predicate
import arrow.core.toNonEmptyListOrNull
import arrow.optics.Every
import arrow.optics.Iso
import arrow.typeclasses.Monoid
Expand Down Expand Up @@ -66,16 +67,17 @@ public fun interface FilterIndex<S, I, A> {
@JvmStatic
public fun <A> nonEmptyList(): FilterIndex<NonEmptyList<A>, Int, A> =
FilterIndex { p ->
object : Every<NonEmptyList<A>, A> {
override fun <R> foldMap(M: Monoid<R>, source: NonEmptyList<A>, map: (A) -> R): R = M.run {
source.foldIndexed(empty()) { index, acc, r ->
if (p(index)) acc.combine(map(r)) else acc
}
}
object : Every<NonEmptyList<A>, A> {
override fun <R> foldMap(M: Monoid<R>, source: NonEmptyList<A>, map: (A) -> R): R = M.run {
source.foldIndexed(empty()) { index, acc, r ->
if (p(index)) acc.combine(map(r)) else acc
}
}

override fun modify(source: NonEmptyList<A>, map: (focus: A) -> A): NonEmptyList<A> =
NonEmptyList.fromListUnsafe(source.mapIndexed { index, a -> if (p(index)) map(a) else a })
}
override fun modify(source: NonEmptyList<A>, map: (focus: A) -> A): NonEmptyList<A> =
source.mapIndexed { index, a -> if (p(index)) map(a) else a }.toNonEmptyListOrNull()
?: throw IndexOutOfBoundsException("Empty list doesn't contain element at index 0.")
}
}

@JvmStatic
Expand Down