arrow
arrow copied to clipboard
["Request"] Bind unary values for Nel
What version are you currently using?
1.2.0-RC
What would you like to see?
Sometimes, inside Raise<Nel<E>>, a failed precondition stops the computation from going further, and short-circuiting is necessary.
Let's image a recursive tree. A node is valid if it satisfies the predicate a(), and all its children satisfy it as well. I'm writing this example with context receivers for readability, but it is already the case now.
sealed class Tree {
class A : Tree() {
context(Raise<Nel<Failure, Unit>>)
override fun validate() = either {
ensure(a()) { nonEmptyListOf(Failure.A()) } // ← here
children.mapOrAccumulate {
it.validate().bind()
}.mapLeft { it.flatten() }
.bind()
}
}
class B : Tree() { … }
}
When there are multiple short-circuiting operations, writing ensure() { nonEmptyListOf() } is inconvenient and makes the code more complicated. Instead, the library could provide a variant of ensure and ensureNotNull that encapsulates this behavior:
@RaiseDSL
public inline fun <Error> Raise<Nel<Error>>.ensure(condition: Boolean, raise: () -> Error) {
contract {
callsInPlace(raise, AT_MOST_ONCE)
returns() implies condition
}
return if (condition) Unit else raise(nonEmptyListOf(raise()))
}
@RaiseDSL
public inline fun <Error, B : Any> Raise<Nel<Error>>.ensureNotNull(value: B?, raise: () -> Error): B {
contract {
callsInPlace(raise, AT_MOST_ONCE)
returns() implies (value != null)
}
return value ?: raise(nonEmptyListOf(raise()))
}
As a bonus, what about a RaiseNel type alias?
It would be convenient to do the same with .bind.
@CLOVIS-AI what if we use the extension function instead? i.e.T.nel(): NonEmptyList<T> would that improve readability?
something like
ensure(...) {
Failure.A().nel()
}
Just my opinion, I think I would prefer to make Raise<E> contextual operations ensure / bind / raise just operate on the E type.. If we add another bind for the inner E in Raise<Nel<E>>, I'm afraid that would make it very confusing for early adopters…