I'm trying to integrate Arrow-kt's Either into an application that needs to parse and validate filter input. This input is a comma separated list of criteria. Either sound like a good candidate for that job because it does both jobs at the same time.
Big-Picture
I've got a parser that takes care of the first step:
interface Parse<out T> {
operator fun invoke(value: String): Either<String, T>
}
object ParseList : Parse<List<String>> {
override fun invoke(value: String): Either<String, List<String>> {
val values =
value
.split(",")
.map { it.trim() }
.filter { it.isNotEmpty() }
return if (values.any()) Either.Right(values)
else Either.Left("Value is not a list.")
}
}
and other parsers that take care of an individual value like a date:
class ParseDate : Parse<LocalDate> {
@Suppress("MemberVisibilityCanBePrivate")
var formatter: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
override fun invoke(value: String) =
runCatching { formatter.parse(value) }
.map { LocalDate.from(it) }
.map { Either.Right(it) }
.getOrElse { Either.Left("Value is not a date.") }
}
Combining these to components results in:
List<Either<String, T>>
so it needs to be converted into this:
Either<String, List<T>>
API
In orther to convert a List of Eithers into an Either of Lists i wrote the merge extension that processes each item and stops at the first Left, but keeps running on Right and collecting their values into a new list that is then return as a new Either:
fun <A, B> List<Either<A, B>>.merge(): Either<A, List<B>> {
val result = mutableListOf<B>()
for (x in this) {
when (x) {
is Either.Right -> result.add(x.value)
is Either.Left -> return Either.Left(x.value)
}
}
return Either.Right(result)
}
class MergeTest : FunSpec({
test("Can stop on left.") {
val list = listOf(
"foo".right(),
"bar".left(),
)
list.merge() shouldBe "bar".left()
}
test("Can merge right.") {
val list = listOf(
"foo".right(),
"bar".right(),
)
list.merge() shouldBe listOf("foo", "bar").right()
}
})
Is it necessary to do this on your own or is there a more clever way?