diff --git a/arrow-core-data/src/main/kotlin/arrow/core/Iterable.kt b/arrow-core-data/src/main/kotlin/arrow/core/Iterable.kt
new file mode 100644
index 000000000..00ee7d28a
--- /dev/null
+++ b/arrow-core-data/src/main/kotlin/arrow/core/Iterable.kt
@@ -0,0 +1,781 @@
+@file:Suppress("unused", "FunctionName")
+
+package arrow.core
+
+import arrow.typeclasses.Eq
+import arrow.typeclasses.Monoid
+import arrow.typeclasses.Semigroup
+import arrow.typeclasses.Show
+import kotlin.collections.foldRight as _foldRight
+
+inline fun Iterable.foldRight(initial: B, operation: (A, acc: B) -> B): B =
+ when (this) {
+ is List -> _foldRight(initial, operation)
+ else -> reversed().fold(initial) { acc, a -> operation(a, acc) }
+ }
+
+fun Iterable.ap(ff: Iterable<(A) -> B>): List =
+ flatMap { a -> ff.map { f -> f(a) } }
+
+inline fun Iterable.traverseEither(f: (A) -> Either): Either> =
+ foldRight>>(emptyList().right()) { a, acc ->
+ f(a).ap(acc.map { bs -> { b: B -> listOf(b) + bs } })
+ }
+
+inline fun Iterable.flatTraverseEither(f: (A) -> Either>): Either> =
+ foldRight>>(emptyList().right()) { a, acc ->
+ f(a).ap(acc.map { bs -> { b: Iterable -> b + bs } })
+ }
+
+inline fun Iterable.traverseEither_(f: (A) -> Either): Either {
+ val void = { _: Unit -> { _: Any? -> Unit } }
+ return foldRight>(Unit.right()) { a, acc ->
+ f(a).ap(acc.map(void))
+ }
+}
+
+fun Iterable>.sequenceEither(): Either> =
+ traverseEither(::identity)
+
+fun Iterable>>.flatSequenceEither(): Either> =
+ flatTraverseEither(::identity)
+
+fun Iterable>.sequenceEither_(): Either =
+ traverseEither_(::identity)
+
+inline fun Iterable.traverseValidated(semigroup: Semigroup, f: (A) -> Validated): Validated> =
+ foldRight>>(emptyList().valid()) { a, acc ->
+ f(a).ap(semigroup, acc.map { bs -> { b: B -> listOf(b) + bs } })
+ }
+
+inline fun Iterable.flatTraverseValidated(semigroup: Semigroup, f: (A) -> Validated>): Validated> =
+ foldRight>>(emptyList().valid()) { a, acc ->
+ f(a).ap(semigroup, acc.map { bs -> { b: Iterable -> b + bs } })
+ }
+
+inline fun Iterable.traverseValidated_(semigroup: Semigroup, f: (A) -> Validated): Validated =
+ foldRight>(Unit.valid()) { a, acc ->
+ f(a).ap(semigroup, acc.map { { Unit } })
+ }
+
+fun Iterable>.sequenceValidated(semigroup: Semigroup): Validated> =
+ traverseValidated(semigroup, ::identity)
+
+fun Iterable>>.flatSequenceValidated(semigroup: Semigroup): Validated> =
+ flatTraverseValidated(semigroup, ::identity)
+
+fun Iterable>.sequenceValidated_(semigroup: Semigroup): Validated =
+ traverseValidated_(semigroup, ::identity)
+
+inline fun Iterable.map2(fb: Iterable, f: (Tuple2) -> Z): List =
+ flatMap { a ->
+ fb.map { b ->
+ f(Tuple2(a, b))
+ }
+ }
+
+fun Iterable.mapConst(b: B): List =
+ map { b }
+
+fun Iterable.void(): List =
+ mapConst(Unit)
+
+fun List.foldRight(lb: Eval, f: (A, Eval) -> Eval): Eval {
+ fun loop(fa_p: List): Eval = when {
+ fa_p.isEmpty() -> lb
+ else -> f(fa_p.first(), Eval.defer { loop(fa_p.drop(1)) })
+ }
+
+ return Eval.defer { loop(this) }
+}
+
+fun Iterable.reduceOrNull(initial: (A) -> B, operation: (acc: B, A) -> B): B? {
+ val iterator = this.iterator()
+ if (!iterator.hasNext()) return null
+ var accumulator: B = initial(iterator.next())
+ while (iterator.hasNext()) {
+ accumulator = operation(accumulator, iterator.next())
+ }
+ return accumulator
+}
+
+inline fun List.reduceRightEvalOrNull(
+ initial: (A) -> B,
+ operation: (A, acc: Eval) -> Eval
+): Eval {
+ val iterator = listIterator(size)
+ if (!iterator.hasPrevious()) return Eval.now(null)
+ var accumulator: Eval = Eval.now(initial(iterator.previous()))
+ while (iterator.hasPrevious()) {
+ accumulator = operation(iterator.previous(), accumulator)
+ }
+ return accumulator
+}
+
+inline fun List.reduceRightNull(
+ initial: (A) -> B,
+ operation: (A, acc: B) -> B
+): B? {
+ val iterator = listIterator(size)
+ if (!iterator.hasPrevious()) return null
+ var accumulator: B = initial(iterator.previous())
+ while (iterator.hasPrevious()) {
+ accumulator = operation(iterator.previous(), accumulator)
+ }
+ return accumulator
+}
+
+/**
+ * Returns a [List>] containing the zipped values of the two lists with null for padding.
+ *
+ * Example:
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * //sampleStart
+ * val padRight = listOf(1, 2).padZip(listOf("a")) // Result: [Tuple2(1, "a"), Tuple2(2, null)]
+ * val padLeft = listOf(1).padZip(listOf("a", "b")) // Result: [Tuple2(1, "a"), Tuple2(null, "b")]
+ * val noPadding = listOf(1, 2).padZip(listOf("a", "b")) // Result: [Tuple2(1, "a"), Tuple2(2, "b")]
+ * //sampleEnd
+ *
+ * fun main() {
+ * println("padRight = $padRight")
+ * println("padLeft = $padLeft")
+ * println("noPadding = $noPadding")
+ * }
+ * ```
+ */
+fun Iterable.padZip(other: Iterable): List> =
+ align(other) { ior ->
+ ior.fold(
+ { it toT null },
+ { null toT it },
+ { a, b -> a toT b }
+ )
+ }
+
+/**
+ * Returns a [ListK] 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).padZip(listOf("a")) { l, r -> l toT r } // Result: [Tuple2(1, "a"), Tuple2(2, null)]
+ * val padZipLeft = listOf(1).padZip(listOf("a", "b")) { l, r -> l toT r } // Result: [Tuple2(1, "a"), Tuple2(null, "b")]
+ * val noPadding = listOf(1, 2).padZip(listOf("a", "b")) { l, r -> l toT r } // Result: [Tuple2(1, "a"), Tuple2(2, "b")]
+ * //sampleEnd
+ *
+ * fun main() {
+ * println("padZipRight = $padZipRight")
+ * println("padZipLeft = $padZipLeft")
+ * println("noPadding = $noPadding")
+ * }
+ * ```
+ */
+inline fun Iterable.padZip(other: Iterable, fa: (A?, B?) -> C): List =
+ padZip(other).map { fa(it.a, it.b) }
+
+/**
+ * Returns a [List] 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).leftPadZip(listOf("a")) { l, r -> l toT r } // Result: [Tuple2(1, "a")]
+ * val right = listOf(1).leftPadZip(listOf("a", "b")) { l, r -> l toT r } // Result: [Tuple2(1, "a"), Tuple2(null, "b")]
+ * val both = listOf(1, 2).leftPadZip(listOf("a", "b")) { l, r -> l toT r } // Result: [Tuple2(1, "a"), Tuple2(2, "b")]
+ * //sampleEnd
+ *
+ * fun main() {
+ * println("left = $left")
+ * println("right = $right")
+ * println("both = $both")
+ * }
+ * ```
+ */
+inline fun Iterable.leftPadZip(other: Iterable, fab: (A?, B) -> C): List =
+ padZip(other) { a: A?, b: B? -> b?.let { fab(a, it) } }.mapNotNull(::identity)
+
+/**
+ * Returns a [List>] containing the zipped values of the two listKs
+ * with null for padding on the left.
+ *
+ * Example:
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * //sampleStart
+ * val padRight = listOf(1, 2).leftPadZip(listOf("a")) // Result: [Tuple2(1, "a")]
+ * val padLeft = listOf(1).leftPadZip(listOf("a", "b")) // Result: [Tuple2(1, "a"), Tuple2(null, "b")]
+ * val noPadding = listOf(1, 2).leftPadZip(listOf("a", "b")) // Result: [Tuple2(1, "a"), Tuple2(2, "b")]
+ * //sampleEnd
+ *
+ * fun main() {
+ * println("left = $left")
+ * println("right = $right")
+ * println("both = $both")
+ * }
+ * ```
+ */
+fun Iterable.leftPadZip(other: Iterable): List> =
+ this.leftPadZip(other) { a, b -> a toT b }
+
+/**
+ * Returns a [List] containing the result of applying some transformation `(A, B?) -> C`
+ * on a zip, excluding all cases where the left value is null.
+ *
+ * Example:
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * //sampleStart
+ * val left = listOf(1, 2).rightPadZip(listOf("a")) { l, r -> l toT r } // Result: [Tuple2(1, "a"), Tuple2(null, "b")]
+ * val right = listOf(1).rightPadZip(listOf("a", "b")) { l, r -> l toT r } // Result: [Tuple2(1, "a")]
+ * val both = listOf(1, 2).rightPadZip(listOf("a", "b")) { l, r -> l toT r } // Result: [Tuple2(1, "a"), Tuple2(2, "b")]
+ * //sampleEnd
+ *
+ * fun main() {
+ * println("left = $left")
+ * println("right = $right")
+ * println("both = $both")
+ * }
+ * ```
+ */
+inline fun Iterable.rightPadZip(other: Iterable, fa: (A, B?) -> C): List =
+ other.leftPadZip(this) { a, b -> fa(b, a) }
+
+/**
+ * Returns a [List>] containing the zipped values of the two listKs
+ * with null for padding on the right.
+ *
+ * Example:
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * //sampleStart
+ * val padRight = listOf(1, 2).rightPadZip(listOf("a")) // Result: [Tuple2(1, "a"), Tuple2(2, null)]
+ * val padLeft = listOf(1).rightPadZip(listOf("a", "b")) // Result: [Tuple2(1, "a")]
+ * val noPadding = listOf(1, 2).rightPadZip(listOf("a", "b")) // Result: [Tuple2(1, "a"), Tuple2(2, "b")]
+ * //sampleEnd
+ *
+ * fun main() {
+ * println("left = $left")
+ * println("right = $right")
+ * println("both = $both")
+ * }
+ * ```
+ */
+fun Iterable.rightPadZip(other: Iterable): List> =
+ this.rightPadZip(other) { a, b -> a toT b }
+
+fun Iterable.show(SA: Show): String = "[" +
+ joinToString(", ") { SA.run { it.show() } } + "]"
+
+@Suppress("UNCHECKED_CAST")
+private tailrec fun go(
+ buf: MutableList,
+ f: (A) -> Iterable>,
+ v: List>
+) {
+ if (v.isNotEmpty()) {
+ when (val head: Either = v.first()) {
+ is Either.Right -> {
+ buf += head.b
+ go(buf, f, v.drop(1))
+ }
+ is Either.Left -> go(buf, f, (f(head.a) + v.drop(1)))
+ }
+ }
+}
+
+fun tailRecMIterable(a: A, f: (A) -> Iterable>): List {
+ val buf = mutableListOf()
+ go(buf, f, f(a).toList())
+ return ListK(buf)
+}
+
+/**
+ * Combines two structures by taking the union of their shapes and combining the elements with the given function.
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result =
+ * listOf("A", "B").align(listOf(1, 2, 3)) {
+ * "$it"
+ * }
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+inline fun Iterable.align(b: Iterable, fa: (Ior) -> C): List =
+ this.align(b).map(fa)
+
+/**
+ * Combines two structures by taking the union of their shapes and using Ior to hold the elements.
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result =
+ * listOf("A", "B").align(listOf(1, 2, 3))
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+fun Iterable.align(b: Iterable): List> =
+ alignRec(this, b)
+
+@Suppress("NAME_SHADOWING")
+private fun alignRec(ls: Iterable, rs: Iterable): List> {
+ val ls = if (ls is List) ls else ls.toList()
+ val rs = if (rs is List) rs else rs.toList()
+ return when {
+ ls.isEmpty() -> rs.map { it.rightIor() }
+ rs.isEmpty() -> ls.map { it.leftIor() }
+ else -> listOf(Ior.Both(ls.first(), rs.first())) + alignRec(ls.drop(1), rs.drop(1))
+ }
+}
+
+/**
+ * aligns two structures and combine them with the given [Semigroup.combine]
+ */
+fun Iterable.salign(
+ SG: Semigroup,
+ other: Iterable
+): Iterable = SG.run {
+ align(other) {
+ it.fold(::identity, ::identity) { a, b ->
+ a.combine(b)
+ }
+ }
+}
+
+/**
+ * unzips the structure holding the resulting elements in an `Tuple2`
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result =
+ * listOf("A" toT 1, "B" toT 2).k().unzip()
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+fun Iterable>.unzip(): Tuple2, List> =
+ fold(emptyList() toT emptyList()) { (l, r), x ->
+ l + x.a toT r + x.b
+ }
+
+/**
+ * after applying the given function unzip the resulting structure into its elements.
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result =
+ * listOf("A:1", "B:2", "C:3").k().unzip { e ->
+ * e.split(":").let {
+ * it.first() toT it.last()
+ * }
+ * }
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+inline fun Iterable.unzip(fc: (C) -> Tuple2): Tuple2, List> =
+ map(fc).unzip()
+
+/**
+ * splits a union into its component parts.
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result =
+ * listOf(("A" toT 1).bothIor(), ("B" toT 2).bothIor(), "C".leftIor())
+ * .unalign()
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+fun Iterable>.unalign(): Tuple2, List> =
+ fold(emptyList() toT emptyList()) { (l, r), x ->
+ x.fold(
+ { l + it toT r },
+ { l toT r + it },
+ { a, b -> l + a toT r + b }
+ )
+ }
+
+/**
+ * after applying the given function, splits the resulting union shaped structure into its components parts
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result =
+ * listOf(1, 2, 3).unalign {
+ * it.leftIor()
+ * }
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+inline fun Iterable.unalign(fa: (C) -> Ior): Tuple2, List> =
+ map(fa).unalign()
+
+fun Iterable.product(fb: Iterable): List> =
+ fb.ap(map { a: A -> { b: B -> Tuple2(a, b) } })
+
+fun Iterable.combineAll(MA: Monoid): A = MA.run {
+ this@combineAll.fold(empty()) { acc, a ->
+ acc.combine(a)
+ }
+}
+
+/**
+ * attempt to split the computation, giving access to the first result.
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result =
+ * listOf("A", "B", "C").split()
+ * //sampleEnd
+ * println(result)
+ * }
+ */
+fun Iterable.split(): Tuple2, A>? =
+ firstOrNull()?.let { first ->
+ Tuple2(tail(), first)
+ }
+
+fun Iterable.tail(): List =
+ drop(1)
+
+/**
+ * interleave both computations in a fair way.
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val tags = List(10) { "#" }
+ * val result =
+ * tags.interleave(listOf("A", "B", "C"))
+ * //sampleEnd
+ * println(result)
+ * }
+ */
+fun Iterable.interleave(other: Iterable): List =
+ this.split()?.let { (fa, a) ->
+ listOf(a) + other.interleave(fa)
+ } ?: other.toList()
+
+/**
+ * Fair conjunction. Similarly to interleave
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result =
+ * listOf(1,2,3).unweave { i -> listOf("$i, ${i + 1}") }
+ * //sampleEnd
+ * println(result)
+ * }
+ */
+fun Iterable.unweave(ffa: (A) -> Iterable): List =
+ split()?.let { (fa, a) ->
+ ffa(a).interleave(fa.unweave(ffa))
+ } ?: emptyList()
+
+/**
+ * Logical conditional. The equivalent of Prolog's soft-cut.
+ * If its first argument succeeds at all, then the results will be
+ * fed into the success branch. Otherwise, the failure branch is taken.
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result =
+ * listOf(1,2,3).ifThen(listOf("empty")) { i ->
+ * listOf("$i, ${i + 1}")
+ * }
+ * //sampleEnd
+ * println(result)
+ * }
+ */
+inline fun Iterable.ifThen(fb: Iterable, ffa: (A) -> Iterable): Iterable =
+ split()?.let { (fa, a) ->
+ ffa(a) + fa.flatMap(ffa)
+ } ?: fb.toList()
+
+fun Iterable>.uniteEither(): List =
+ flatMap { either ->
+ either.fold({ emptyList() }, { b -> listOf(b) })
+ }
+
+fun Iterable>.uniteValidated(): List =
+ flatMap { validated ->
+ validated.fold({ emptyList() }, { b -> listOf(b) })
+ }
+
+/**
+ * Separate the inner [Either] values into the [Either.Left] and [Either.Right].
+ *
+ * @receiver Iterable of Validated
+ * @return a tuple containing List with [Either.Left] and another List with its [Either.Right] values.
+ */
+fun Iterable>.separateEither(): Tuple2, List> {
+ val asep = flatMap { gab -> gab.fold({ listOf(it) }, { emptyList() }) }
+ val bsep = flatMap { gab -> gab.fold({ emptyList() }, { listOf(it) }) }
+ return Tuple2(asep, bsep)
+}
+
+/**
+ * Separate the inner [Validated] values into the [Validated.Invalid] and [Validated.Valid].
+ *
+ * @receiver Iterable of Validated
+ * @return a tuple containing List with [Validated.Invalid] and another List with its [Validated.Valid] values.
+ */
+fun Iterable>.separateValidated(): Tuple2, List> {
+ val asep = flatMap { gab -> gab.fold({ listOf(it) }, { emptyList() }) }
+ val bsep = flatMap { gab -> gab.fold({ emptyList() }, { listOf(it) }) }
+ return Tuple2(asep, bsep)
+}
+
+fun Iterable>.flatten(): List =
+ flatMap(::identity)
+
+inline fun Iterable.ifM(ifFalse: () -> Iterable, ifTrue: () -> Iterable): List =
+ flatMap { bool ->
+ if (bool) ifTrue() else ifFalse()
+ }
+
+fun Iterable>.selectM(f: Iterable<(A) -> B>): List =
+ flatMap { it.fold({ a -> f.map { ff -> ff(a) } }, { b -> listOf(b) }) }
+
+/**
+ * Applies [f] to an [A] inside [Iterable] and returns the [List] structure with a tuple of the [A] value and the
+ * computed [B] value as result of applying [f]
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * val result =
+ * //sampleStart
+ * listOf("Hello").fproduct { "$it World" }
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+inline fun Iterable.fproduct(f: (A) -> B): List> =
+ map { a -> Tuple2(a, f(a)) }
+
+/**
+ * Pairs [B] with [A] returning a List>
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * val result =
+ * //sampleStart
+ * listOf("Hello", "Hello2").tupleLeft("World")
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+fun Iterable.tupleLeft(b: B): List> =
+ map { a -> Tuple2(b, a) }
+
+/**
+ * Pairs [A] with [B] returning a List>
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * val result =
+ * //sampleStart
+ * listOf("Hello").tupleRight("World")
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+fun Iterable.tupleRight(b: B): List> =
+ map { a -> Tuple2(a, b) }
+
+/**
+ * Given [A] is a sub type of [B], re-type this value from Iterable to Iterable
+ *
+ * Kind -> Kind
+ *
+ * ```kotlin:ank:playground
+ * import arrow.core.*
+ *
+ * fun main(args: Array) {
+ * //sampleStart
+ * val result: Iterable =
+ * listOf("Hello World").widen()
+ * //sampleEnd
+ * println(result)
+ * }
+ * ```
+ */
+fun Iterable.widen(): Iterable =
+ this
+
+fun List.widen(): List =
+ this
+
+fun Iterable.fold(MA: Monoid): A = MA.run {
+ this@fold.fold(empty()) { acc, a ->
+ acc.combine(a)
+ }
+}
+
+fun Iterable.foldMap(MB: Monoid, f: (A) -> B): B = MB.run {
+ this@foldMap.fold(empty()) { acc, a ->
+ acc.combine(f(a))
+ }
+}
+
+fun listEq(EQA: Eq): Eq> =
+ ListEq(EQA)
+
+private class ListEq(private val EQA: Eq) : Eq> {
+ override fun List.eqv(b: List): Boolean =
+ eqv(EQA, b)
+}
+
+fun Iterable.eqv(EQA: Eq, other: Iterable): Boolean = EQA.run {
+ if (this is Collection<*> && other is Collection && this.size != other.size) false
+ else {
+ zip(other) { a, b -> a.eqv(b) }
+ .fold(true) { acc, bool -> acc && bool }
+ }
+}
+
+fun Iterable.neqv(EQA: Eq, other: Iterable): Boolean =
+ !eqv(EQA, other)
+
+fun Iterable.crosswalk(f: (A) -> Iterable): List> =
+ fold(emptyList()) { bs, a ->
+ f(a).align(bs) { ior ->
+ ior.fold(
+ { listOf(it) },
+ ::identity,
+ { l, r -> listOf(l) + r }
+ )
+ }
+ }
+
+fun Iterable.crosswalkMap(f: (A) -> Map): Map> =
+ fold(emptyMap()) { bs, a ->
+ f(a).align(bs) { ior ->
+ ior.fold(
+ { listOf(it) },
+ ::identity,
+ { l, r -> listOf(l) + r }
+ )
+ }
+ }
+
+fun Iterable.crosswalkNull(f: (A) -> B?): List? =
+ fold?>(emptyList()) { bs, a ->
+ Ior.fromNullables(f(a), bs)?.fold(
+ { listOf(it) },
+ ::identity,
+ { l, r -> listOf(l) + r }
+ )
+ }
+
+@PublishedApi
+internal val unit: List =
+ listOf(Unit)
+
+@JvmName("product3")
+fun Iterable>.product(other: Iterable): List> =
+ map2(other) { (ab, c) -> Tuple3(ab.a, ab.b, c) }
+
+@JvmName("product4")
+fun Iterable>.product(other: Iterable): List> =
+ map2(other) { (abc, d) -> Tuple4(abc.a, abc.b, abc.c, d) }
+
+@JvmName("product5")
+fun Iterable>.product(other: Iterable): List> =
+ map2(other) { (abcd, e) -> Tuple5(abcd.a, abcd.b, abcd.c, abcd.d, e) }
+
+@JvmName("product6")
+fun Iterable>.product(other: Iterable): List> =
+ map2(other) { (abcde, f) -> Tuple6(abcde.a, abcde.b, abcde.c, abcde.d, abcde.e, f) }
+
+@JvmName("product7")
+fun Iterable>.product(
+ other: Iterable