arrow icon indicating copy to clipboard operation
arrow copied to clipboard

["Request"] Bind unary values for Nel

Open CLOVIS-AI opened this issue 2 years ago • 2 comments

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?

CLOVIS-AI avatar Apr 29 '23 16:04 CLOVIS-AI

It would be convenient to do the same with .bind.

CLOVIS-AI avatar Apr 29 '23 19:04 CLOVIS-AI

@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…

myuwono avatar Apr 29 '23 22:04 myuwono