-
|
I'm using arrow-core 1.2.0 I think I'm missing some kind of flatMapOrAcculate function. Consider the example from the documentation Book class on creation returns the following: Either<NonEmptyList, Book> and the Author invoke method: Either<EmptyAuthorName, Author>. Now imagine that Author class also returns a list of errors because there are multiple validations, so the return type is Either<Nel, Author> it acutally makes the compiler not complaing but the behavior is different - now i only receive the errors from the first Author from the list, disregarding the remaining authors and the book validations. Sample code: sealed interface BookValidationError
object EmptyTitle: BookValidationError
object EmptyAuthorName: BookValidationError
data class InvalidAuthorName(val name: String ): BookValidationError
object NoAuthors: BookValidationError
data class EmptyAuthor(val index: Int): BookValidationError
data class Author private constructor(val name: String) {
companion object {
operator fun invoke(name: String): Either<Nel<BookValidationError>, Author> = either {
zipOrAccumulate(
{ ensure(name.isNotEmpty()) { EmptyAuthorName } },
{ ensure(name.length > 3) { InvalidAuthorName(name)} }
) { _, _ ->
Author(name)
}
}
}
}
data class Book private constructor(
val title: String, val authors: NonEmptyList<Author>
) {
companion object {
operator fun invoke(
title: String, authors: Iterable<String>
): Either<NonEmptyList<BookValidationError>, Book> = either {
zipOrAccumulate(
{ checkTitle(title) },
{
val validatedAuthors = authors.mapOrAccumulate { Author(it) }.bindAll()
ensureNotNull(validatedAuthors.toNonEmptyListOrNull()) { NoAuthors }
}
) { _, authorsNel ->
Book(title, authorsNel)
}
}
private fun checkTitle(title: String) = either {
ensure(title.isNotEmpty()) { EmptyTitle }
}
}
}
after creating a book: Book("", listOf("", "x")) i'd expect to receive EmptyTitle, EmptyAuthorName and InvalidAuthorName twice yet all i get is Either.Left(NonEmptyList(EmptyAuthorName@5b80350b, InvalidAuthorName(name=))) What am I missing here? how to flatMap this structure? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
|
Actually managed to get it working with using custom combine function in both mapOrAccumulate and zipOrAccumualte combine = { e1, e2 -> e1 + e2 } But i believe the default behaviour is just confusing and easy to make a mistake |
Beta Was this translation helpful? Give feedback.
-
|
I believe what you were missing in the original example is data class Author private constructor(val name: String) {
companion object {
context(_: RaiseAccumulate<BookValidationError>)
operator fun invoke(name: String): Author {
ensureOrAccumulate(name.isNotEmpty()) { EmptyAuthorName }
ensureOrAccumulate(name.length > 3) { InvalidAuthorName(name) }
return Author(name)
}
}
}
data class Book private constructor(
val title: String, val authors: NonEmptyList<Author>
) {
companion object {
context(_: RaiseAccumulate<BookValidationError>)
operator fun invoke(title: String, authors: Iterable<String>): Book {
ensureOrAccumulate(title.isNotEmpty()) { EmptyTitle }
return Book(title, authors.map { Author(it) }.toNonEmptyListOrNull() ?: raise(NoAuthors))
}
}
} |
Beta Was this translation helpful? Give feedback.
I believe what you were missing in the original example is
bindNelOrAccumulate, which exists in current Arrow. Using a combine function worked because then you replacedbindAllwithbind, which then resolved to the innerRaiseAccumulate.I would rewrite your code like this in modern Arrow: